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

1
.gitignore vendored
View File

@@ -139,3 +139,4 @@ assets/*
temp/* temp/*
.turbo .turbo
data/* data/*
release

10
.vscode/launch.json vendored
View File

@@ -1,5 +1,6 @@
{ {
"configurations": [ "configurations": [
{ {
"name": "Launch Chrome", "name": "Launch Chrome",
"request": "launch", "request": "launch",
@@ -110,6 +111,15 @@
"request": "launch", "request": "launch",
"command": "pnpm run deploy", "command": "pnpm run deploy",
"cwd": "${workspaceFolder}" "cwd": "${workspaceFolder}"
},{
"type": "bun",
"name": "Run Script: build",
"request": "launch",
"env": {
"mode": "build"
},
"program": "index.ts",
"cwd": "${workspaceFolder}"
}, },
] ]
} }

View File

@@ -3,6 +3,9 @@ import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks' import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh' import reactRefresh from 'eslint-plugin-react-refresh'
import baseConfig from '@aklive2d/eslint-config' 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} */ /** @type {import('eslint').Config} */
export default [ export default [
@@ -27,4 +30,21 @@ export default [
], ],
}, },
}, },
...tsConfig,
{
files: ['**/*.js', '**/*.ts'],
languageOptions: {
ecmaVersion: 2022,
globals: {
...globals.browser,
},
},
rules: {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_' },
],
},
},
] ]

View File

@@ -5,9 +5,9 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev:directory": "vite --clearScreen false", "dev:directory": "vite --clearScreen false",
"build": "mode=build node runner.js", "build": "mode=build bun runner.ts",
"preview:directory": "vite preview", "preview:directory": "vite preview",
"lint": "eslint \"src/**/*.js\" \"src/**/*.jsx\" && stylelint \"src/**/*.css\" \"src/**/*.scss\" && prettier --check ." "lint": "eslint && stylelint \"src/**/*.css\" \"src/**/*.scss\" && prettier --check ."
}, },
"dependencies": { "dependencies": {
"react": "^19.0.0", "react": "^19.0.0",
@@ -28,6 +28,11 @@
"@aklive2d/module": "workspace:*", "@aklive2d/module": "workspace:*",
"@aklive2d/prettier-config": "workspace:*" "@aklive2d/prettier-config": "workspace:*"
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"devDependencies": { "devDependencies": {
"@types/react": "^19.0.8", "@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3", "@types/react-dom": "^19.0.3",

View File

@@ -1,11 +1,15 @@
import path from 'node:path'
import { DIST_DIR } from '@aklive2d/showcase'
import { build as viteBuild } from 'vite' import { build as viteBuild } from 'vite'
import { envParser } from '@aklive2d/libs' import { envParser, file } from '@aklive2d/libs'
const build = async (namesToBuild) => { const build = async (namesToBuild: string[]) => {
if (!namesToBuild.length) { if (!namesToBuild.length) {
// skip as directory can only build // skip as directory can only build
// when all operators are built // when all operators are built
await viteBuild() await viteBuild()
const releaseDir = path.resolve(DIST_DIR)
file.rmdir(releaseDir)
} }
} }
@@ -18,7 +22,7 @@ async function main() {
default: [], default: [],
}, },
}) })
await build(name) await build(name as string[])
} }
main() main()

View File

@@ -192,7 +192,7 @@ export default function Home() {
> >
{officialUpdate.dates {officialUpdate.dates
.reduce((acc, cur) => { .reduce((acc, cur) => {
const op = officialUpdate[cur] const op = officialUpdate.info[cur]
return [...acc, ...op] return [...acc, ...op]
}, []) }, [])
.slice( .slice(

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": ["spine-ts/**/*"]
}

View File

@@ -8,12 +8,12 @@ import { copyDirectoryData } from '@aklive2d/vite-helpers'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig(async () => { export default defineConfig(async () => {
const dataDir = path.resolve(import.meta.dirname, config.dir_name.data) const dataDir = path.resolve(import.meta.dirname, config.dir_name.data)
const publicDir = path.resolve(showcaseDirs.DIST_DIR) const releaseDir = path.resolve(showcaseDirs.DIST_DIR)
await copyDirectoryData({ dataDir, publicDir }) await copyDirectoryData({ dataDir, publicDir: releaseDir })
return { return {
envDir: dataDir, envDir: dataDir,
plugins: [react()], plugins: [react()],
publicDir, publicDir: releaseDir,
resolve: { resolve: {
alias: { alias: {
'@': path.resolve('./src'), '@': path.resolve('./src'),
@@ -22,7 +22,12 @@ export default defineConfig(async () => {
}, },
build: { build: {
emptyOutDir: false, emptyOutDir: false,
outDir: publicDir, outDir: path.resolve(
import.meta.dirname,
'..',
'..',
config.dir_name.dist
),
rollupOptions: { rollupOptions: {
output: { output: {
entryFileNames: `${config.directory.assets_dir}/[name]-[hash:8].js`, entryFileNames: `${config.directory.assets_dir}/[name]-[hash:8].js`,

View File

@@ -1,18 +1,15 @@
import eslint from '@eslint/js' import { tsConfig } from '@aklive2d/eslint-config'
import tseslint from 'typescript-eslint' import tseslint from 'typescript-eslint'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
import globals from 'globals' import globals from 'globals'
/** @type {import('eslint').Config} */ /** @type {import('eslint').Config} */
export default tseslint.config( export default tseslint.config(
eslint.configs.recommended, ...tsConfig,
tseslint.configs.recommended,
eslintPluginPrettierRecommended,
{ {
ignores: ['dist', 'spine-ts'], ignores: ['dist', 'spine-ts'],
}, },
{ {
files: ['**/*.{js,jsx}', '**/*.{ts,tsx}'], files: ['**/*.js', '**/*.ts'],
languageOptions: { languageOptions: {
ecmaVersion: 2022, ecmaVersion: 2022,
globals: { globals: {

View File

@@ -1,3 +1,4 @@
import { Player } from './src/player.ts' import { Player, PlayerConfig } from './src/player.ts'
export { Player } export { Player }
export type { PlayerConfig }

View File

@@ -6,13 +6,15 @@
"scripts": { "scripts": {
"lint": "eslint && prettier --check ." "lint": "eslint && prettier --check ."
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"devDependencies": { "devDependencies": {
"@aklive2d/postcss-config": "workspace:*", "@aklive2d/postcss-config": "workspace:*",
"@aklive2d/prettier-config": "workspace:*", "@aklive2d/prettier-config": "workspace:*",
"@aklive2d/stylelint-config": "workspace:*", "@aklive2d/stylelint-config": "workspace:*",
"eslint-plugin-prettier": "^5.2.6", "@aklive2d/eslint-config": "workspace:*"
"globals": "^16.0.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.31.1"
} }
} }

View File

@@ -1,3 +1,29 @@
import baseConfig from '@aklive2d/eslint-config' 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} */ /** @type {import('eslint').Config} */
export default [...baseConfig, { ignores: ['src/libs/*'] }] export default tseslint.config(
...baseConfig,
...tsConfig,
{
ignores: ['dist', 'spine-ts'],
},
{
files: ['**/*.js', '**/*.ts'],
languageOptions: {
ecmaVersion: 2022,
globals: {
...globals.browser,
},
},
rules: {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_' },
],
},
}
)

View File

@@ -12,5 +12,5 @@ export const DIST_DIR = path.resolve(
import.meta.dirname, import.meta.dirname,
'..', '..',
'..', '..',
config.dir_name.dist config.app.showcase.release
) )

View File

@@ -3,12 +3,17 @@
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"main": "index.js", "main": "index.ts",
"scripts": { "scripts": {
"dev:showcase": "vite --clearScreen false", "dev:showcase": "vite --clearScreen false",
"build": "mode=build node runner.js", "build": "mode=build bun runner.ts",
"preview:showcase": "vite preview", "preview:showcase": "vite preview",
"lint": "eslint \"src/**/*.js\" && stylelint \"**/*.css\" && prettier --check ." "lint": "eslint && prettier --check ."
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
}, },
"devDependencies": { "devDependencies": {
"vite": "^6.1.5", "vite": "^6.1.5",

View File

@@ -5,7 +5,7 @@ import { envParser, file } from '@aklive2d/libs'
import { copyShowcaseData, copyProjectJSON } from '@aklive2d/vite-helpers' import { copyShowcaseData, copyProjectJSON } from '@aklive2d/vite-helpers'
import * as dirs from './index.js' import * as dirs from './index.js'
const build = async (namesToBuild) => { const build = async (namesToBuild: string[]) => {
const names = !namesToBuild.length ? Object.keys(operators) : namesToBuild const names = !namesToBuild.length ? Object.keys(operators) : namesToBuild
console.log('Generating assets for', names.length, 'operators') console.log('Generating assets for', names.length, 'operators')
for (const name of names) { for (const name of names) {
@@ -32,7 +32,7 @@ async function main() {
default: [], default: [],
}, },
}) })
await build(name) await build(name as string[])
} }
main() main()

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": ["spine-ts/**/*"]
}

102
bun.lock
View File

@@ -4,12 +4,14 @@
"": { "": {
"name": "aklive2d", "name": "aklive2d",
"devDependencies": { "devDependencies": {
"@types/jsdom": "^21.1.7",
"cz-conventional-changelog": "^3.3.0", "cz-conventional-changelog": "^3.3.0",
"eslint": "^9.25.1", "eslint": "^9.25.1",
"http-server": "^14.1.1", "http-server": "^14.1.1",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"stylelint": "^16.19.1", "stylelint": "^16.19.1",
"turbo": "^2.5.2", "turbo": "^2.5.2",
"typescript": "5.8.2",
}, },
}, },
"apps/directory": { "apps/directory": {
@@ -46,18 +48,25 @@
"sass": "^1.84.0", "sass": "^1.84.0",
"vite": "^6.1.5", "vite": "^6.1.5",
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
}, },
"apps/module": { "apps/module": {
"name": "@aklive2d/module", "name": "@aklive2d/module",
"version": "0.0.0", "version": "0.0.0",
"devDependencies": { "devDependencies": {
"@aklive2d/eslint-config": "workspace:*",
"@aklive2d/postcss-config": "workspace:*", "@aklive2d/postcss-config": "workspace:*",
"@aklive2d/prettier-config": "workspace:*", "@aklive2d/prettier-config": "workspace:*",
"@aklive2d/stylelint-config": "workspace:*", "@aklive2d/stylelint-config": "workspace:*",
"eslint-plugin-prettier": "^5.2.6", },
"globals": "^16.0.0", "peerDependencies": {
"typescript": "^5.8.3", "globals": ">=16.0.0",
"typescript-eslint": "^8.31.1", "typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
}, },
}, },
"apps/showcase": { "apps/showcase": {
@@ -76,6 +85,11 @@
"@aklive2d/vite-helpers": "workspace:*", "@aklive2d/vite-helpers": "workspace:*",
"vite": "^6.1.5", "vite": "^6.1.5",
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
}, },
"packages/assets": { "packages/assets": {
"name": "@aklive2d/assets", "name": "@aklive2d/assets",
@@ -89,6 +103,11 @@
"@aklive2d/operator": "workspace:*", "@aklive2d/operator": "workspace:*",
"@aklive2d/prettier-config": "workspace:*", "@aklive2d/prettier-config": "workspace:*",
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
}, },
"packages/background": { "packages/background": {
"name": "@aklive2d/background", "name": "@aklive2d/background",
@@ -101,6 +120,11 @@
"@aklive2d/prettier-config": "workspace:*", "@aklive2d/prettier-config": "workspace:*",
"sharp": "^0.33.5", "sharp": "^0.33.5",
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
}, },
"packages/charword-table": { "packages/charword-table": {
"name": "@aklive2d/charword-table", "name": "@aklive2d/charword-table",
@@ -113,6 +137,11 @@
"@aklive2d/operator": "workspace:*", "@aklive2d/operator": "workspace:*",
"@aklive2d/prettier-config": "workspace:*", "@aklive2d/prettier-config": "workspace:*",
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
}, },
"packages/config": { "packages/config": {
"name": "@aklive2d/config", "name": "@aklive2d/config",
@@ -122,6 +151,11 @@
"@aklive2d/libs": "workspace:*", "@aklive2d/libs": "workspace:*",
"@aklive2d/prettier-config": "workspace:*", "@aklive2d/prettier-config": "workspace:*",
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
}, },
"packages/downloader": { "packages/downloader": {
"name": "@aklive2d/downloader", "name": "@aklive2d/downloader",
@@ -140,8 +174,10 @@
"@eslint/js": "^9.19.0", "@eslint/js": "^9.19.0",
"eslint": "^9.19.0", "eslint": "^9.19.0",
"eslint-config-prettier": "^10.0.1", "eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.3", "eslint-plugin-prettier": "^5.2.6",
"globals": "^15.14.0", "globals": "^16.0.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.31.1",
}, },
}, },
"packages/libs": { "packages/libs": {
@@ -155,6 +191,15 @@
"yauzl-promise": "^4.0.0", "yauzl-promise": "^4.0.0",
"yazl": "^3.3.1", "yazl": "^3.3.1",
}, },
"devDependencies": {
"@types/yauzl-promise": "^4.0.1",
"@types/yazl": "^2.4.6",
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
}, },
"packages/music": { "packages/music": {
"name": "@aklive2d/music", "name": "@aklive2d/music",
@@ -166,6 +211,11 @@
"@aklive2d/libs": "workspace:*", "@aklive2d/libs": "workspace:*",
"@aklive2d/prettier-config": "workspace:*", "@aklive2d/prettier-config": "workspace:*",
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
}, },
"packages/official-info": { "packages/official-info": {
"name": "@aklive2d/official-info", "name": "@aklive2d/official-info",
@@ -177,6 +227,11 @@
"@aklive2d/prettier-config": "workspace:*", "@aklive2d/prettier-config": "workspace:*",
"jsdom": "^26.0.0", "jsdom": "^26.0.0",
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
}, },
"packages/operator": { "packages/operator": {
"name": "@aklive2d/operator", "name": "@aklive2d/operator",
@@ -189,6 +244,11 @@
"@aklive2d/prettier-config": "workspace:*", "@aklive2d/prettier-config": "workspace:*",
"yaml": "^2.7.0", "yaml": "^2.7.0",
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
}, },
"packages/postcss-config": { "packages/postcss-config": {
"name": "@aklive2d/postcss-config", "name": "@aklive2d/postcss-config",
@@ -202,6 +262,7 @@
"version": "0.0.0", "version": "0.0.0",
"peerDependencies": { "peerDependencies": {
"prettier": ">=3.0.0", "prettier": ">=3.0.0",
"typescript": ">=5.8.2",
}, },
}, },
"packages/project-json": { "packages/project-json": {
@@ -217,6 +278,11 @@
"@aklive2d/operator": "workspace:*", "@aklive2d/operator": "workspace:*",
"@aklive2d/prettier-config": "workspace:*", "@aklive2d/prettier-config": "workspace:*",
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
}, },
"packages/stylelint-config": { "packages/stylelint-config": {
"name": "@aklive2d/stylelint-config", "name": "@aklive2d/stylelint-config",
@@ -240,6 +306,11 @@
"@aklive2d/operator": "workspace:*", "@aklive2d/operator": "workspace:*",
"@aklive2d/prettier-config": "workspace:*", "@aklive2d/prettier-config": "workspace:*",
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
}, },
"packages/wrangler": { "packages/wrangler": {
"name": "@aklive2d/wrangler", "name": "@aklive2d/wrangler",
@@ -256,6 +327,11 @@
"@aklive2d/showcase": "workspace:*", "@aklive2d/showcase": "workspace:*",
"p-throttle": "^7.0.0", "p-throttle": "^7.0.0",
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
}, },
}, },
"trustedDependencies": [ "trustedDependencies": [
@@ -597,6 +673,8 @@
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
"@types/jsdom": ["@types/jsdom@21.1.7", "", { "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", "parse5": "^7.0.0" } }, "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/node": ["@types/node@22.15.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw=="], "@types/node": ["@types/node@22.15.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw=="],
@@ -605,6 +683,12 @@
"@types/react-dom": ["@types/react-dom@19.1.3", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg=="], "@types/react-dom": ["@types/react-dom@19.1.3", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg=="],
"@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="],
"@types/yauzl-promise": ["@types/yauzl-promise@4.0.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-qYEC3rJwqiJpdQ9b+bPNeuSY0c3JUM8vIuDy08qfuVN7xHm3ZDsHn2kGphUIB0ruEXrPGNXZ64nMUcu4fDjViQ=="],
"@types/yazl": ["@types/yazl@2.4.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-/ifFjQtcKaoZOjl5NNCQRR0fAKafB3Foxd7J/WvFPTMea46zekapcR30uzkwIkKAAuq5T6d0dkwz754RFH27hg=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.31.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.31.1", "@typescript-eslint/type-utils": "8.31.1", "@typescript-eslint/utils": "8.31.1", "@typescript-eslint/visitor-keys": "8.31.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ=="], "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.31.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.31.1", "@typescript-eslint/type-utils": "8.31.1", "@typescript-eslint/utils": "8.31.1", "@typescript-eslint/visitor-keys": "8.31.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.31.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.31.1", "@typescript-eslint/types": "8.31.1", "@typescript-eslint/typescript-estree": "8.31.1", "@typescript-eslint/visitor-keys": "8.31.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@8.31.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.31.1", "@typescript-eslint/types": "8.31.1", "@typescript-eslint/typescript-estree": "8.31.1", "@typescript-eslint/visitor-keys": "8.31.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q=="],
@@ -919,7 +1003,7 @@
"global-prefix": ["global-prefix@3.0.0", "", { "dependencies": { "ini": "^1.3.5", "kind-of": "^6.0.2", "which": "^1.3.1" } }, "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg=="], "global-prefix": ["global-prefix@3.0.0", "", { "dependencies": { "ini": "^1.3.5", "kind-of": "^6.0.2", "which": "^1.3.1" } }, "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg=="],
"globals": ["globals@15.15.0", "", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="], "globals": ["globals@16.0.0", "", {}, "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A=="],
"globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
@@ -1443,7 +1527,7 @@
"typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="], "typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
"typescript-eslint": ["typescript-eslint@8.31.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.31.1", "@typescript-eslint/parser": "8.31.1", "@typescript-eslint/utils": "8.31.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-j6DsEotD/fH39qKzXTQRwYYWlt7D+0HmfpOK+DVhwJOFLcdmn92hq3mBb7HlKJHbjjI/gTOqEcc9d6JfpFf/VA=="], "typescript-eslint": ["typescript-eslint@8.31.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.31.1", "@typescript-eslint/parser": "8.31.1", "@typescript-eslint/utils": "8.31.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-j6DsEotD/fH39qKzXTQRwYYWlt7D+0HmfpOK+DVhwJOFLcdmn92hq3mBb7HlKJHbjjI/gTOqEcc9d6JfpFf/VA=="],
@@ -1509,7 +1593,7 @@
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"@aklive2d/module/globals": ["globals@16.0.0", "", {}, "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A=="], "@aklive2d/eslint-config/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"@commitlint/config-validator/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], "@commitlint/config-validator/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],

View File

@@ -1,37 +1,42 @@
{ {
"private": true, "private": true,
"scripts": { "scripts": {
"build": "turbo run build", "build": "turbo run build",
"dev:showcase": "turbo run dev:showcase --ui tui", "dev:showcase": "turbo run dev:showcase --ui tui",
"preview:showcase": "turbo run preview:showcase --ui tui", "preview:showcase": "turbo run preview:showcase --ui tui",
"dev:directory": "turbo run dev:directory --ui tui", "dev:directory": "turbo run dev:directory --ui tui",
"preview:directory": "turbo run preview:directory --ui tui", "preview:directory": "turbo run preview:directory --ui tui",
"preview": "http-server ./dist", "preview": "http-server ./dist",
"lint": "turbo run lint", "lint": "turbo run lint",
"update": "turbo run update", "update": "turbo run update",
"init": "turbo run init", "init": "turbo run init",
"download:game": "turbo run download:game", "download:game": "turbo run download:game",
"build:cleanup": "turbo run build:cleanup" "build:cleanup": "turbo run build:cleanup"
}, },
"devDependencies": { "devDependencies": {
"cz-conventional-changelog": "^3.3.0", "@types/jsdom": "^21.1.7",
"eslint": "^9.25.1", "cz-conventional-changelog": "^3.3.0",
"http-server": "^14.1.1", "eslint": "^9.25.1",
"prettier": "^3.5.3", "http-server": "^14.1.1",
"stylelint": "^16.19.1", "prettier": "^3.5.3",
"turbo": "^2.5.2" "stylelint": "^16.19.1",
}, "turbo": "^2.5.2",
"name": "aklive2d", "typescript": "5.8.2"
"type": "module", },
"config": { "name": "aklive2d",
"commitizen": { "type": "module",
"path": "cz-conventional-changelog" "config": {
} "commitizen": {
}, "path": "cz-conventional-changelog"
"packageManager": "bun@1.2.11", }
"workspaces": ["packages/*", "apps/*"], },
"trustedDependencies": [ "packageManager": "bun@1.2.11",
"@parcel/watcher", "workspaces": [
"@swc/core" "packages/*",
] "apps/*"
],
"trustedDependencies": [
"@parcel/watcher",
"@swc/core"
]
} }

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} */ /** @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

@@ -1,12 +1,13 @@
import path from 'node:path' import path from 'node:path'
import config from '@aklive2d/config' import config from '@aklive2d/config'
import { yaml } from '@aklive2d/libs' import { yaml } from '@aklive2d/libs'
import type { Config } from './types.ts'
export const DIST_DIR = path.resolve(import.meta.dirname, config.dir_name.dist) export const DIST_DIR = path.resolve(import.meta.dirname, config.dir_name.dist)
export const CONFIG_PATH = path.resolve( export const CONFIG_PATH = path.resolve(
import.meta.dirname, import.meta.dirname,
config.module.assets.config_yaml config.module.assets.config_yaml
) )
const selfConfig = yaml.read(CONFIG_PATH) const selfConfig: Config = yaml.read(CONFIG_PATH)
export default selfConfig export default selfConfig

View File

@@ -1,9 +1,9 @@
import path from 'node:path' import path from 'node:path'
import { file } from '@aklive2d/libs' import { file } from '@aklive2d/libs'
import config from '@aklive2d/config' import config from '@aklive2d/config'
import { DIST_DIR } from '../index.js' import { DIST_DIR } from '../index.ts'
export default async (packageDir) => { export default async (packageDir: string) => {
const copyQueue = [ const copyQueue = [
{ {
fn: file.symlink, fn: file.symlink,

View File

@@ -1,22 +1,23 @@
import path from 'node:path' import path from 'node:path'
import { file } from '@aklive2d/libs' import { file } from '@aklive2d/libs'
import { unzipDownload } from '@aklive2d/downloader' import { unzipDownload, type UnzipDownloadItem } from '@aklive2d/downloader'
import { getOperatorId, getOperatorAlternativeId } from '@aklive2d/operator' import { getOperatorId, getOperatorAlternativeId } from '@aklive2d/operator'
import { mapping } from '@aklive2d/music' import { mapping } from '@aklive2d/music'
import config from '../index.js' import config from '../index.ts'
import type { UpdateList, ItemToDownload, AbInfosItem } from '../types.ts'
export default async (dataDir) => { export default async (dataDir: string) => {
const pidSet = new Set() const pidSet: Set<string> = new Set()
const versionRes = await fetch( const versionRes: Response = await fetch(
'https://ak-conf.hypergryph.com/config/prod/official/Android/version' 'https://ak-conf.hypergryph.com/config/prod/official/Android/version'
) )
const version = (await versionRes.json()).resVersion const version: string = (await versionRes.json()).resVersion
const lpacksRes = await fetch( const lpacksRes: Response = await fetch(
`https://ak.hycdn.cn/assetbundle/official/Android/assets/${version}/hot_update_list.json` `https://ak.hycdn.cn/assetbundle/official/Android/assets/${version}/hot_update_list.json`
) )
const updateList = await lpacksRes.json() const updateList: UpdateList = await lpacksRes.json()
const itemToDownload = new Set(config.item_to_download) const itemToDownload: Set<ItemToDownload> = new Set(config.item_to_download)
updateList.abInfos.map((item) => { updateList.abInfos.map((item: AbInfosItem) => {
if (item.name.includes(config.dynchars)) { if (item.name.includes(config.dynchars)) {
const id = getOperatorId(item.name).replace('.ab', '') const id = getOperatorId(item.name).replace('.ab', '')
itemToDownload.add(id) itemToDownload.add(id)
@@ -32,12 +33,12 @@ export default async (dataDir) => {
const itemToDownloadRegExp = new RegExp( const itemToDownloadRegExp = new RegExp(
`(.*)(${Array.from(itemToDownload).join('|')})(.*)` `(.*)(${Array.from(itemToDownload).join('|')})(.*)`
) )
updateList.abInfos.map((item) => { updateList.abInfos.map((item: AbInfosItem) => {
if (itemToDownloadRegExp.test(item.name)) { if (itemToDownloadRegExp.test(item.name)) {
item.pid && pidSet.add(item.pid) if (item.pid) pidSet.add(item.pid)
} }
}) })
const lpacksToDownload = [] const lpacksToDownload: UnzipDownloadItem[] = []
pidSet.forEach((item) => { pidSet.forEach((item) => {
lpacksToDownload.push({ lpacksToDownload.push({
name: item, name: item,

View File

@@ -1,7 +1,7 @@
{ {
"name": "@aklive2d/assets", "name": "@aklive2d/assets",
"version": "0.0.0", "version": "0.0.0",
"main": "index.js", "main": "index.ts",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -13,10 +13,15 @@
"@aklive2d/operator": "workspace:*", "@aklive2d/operator": "workspace:*",
"@aklive2d/music": "workspace:*" "@aklive2d/music": "workspace:*"
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"scripts": { "scripts": {
"build": "mode=build node runner.js", "build": "mode=build bun runner.ts",
"download:game": "mode=download node runner.js", "download:game": "mode=download bun runner.ts",
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check .", "lint": "eslint && prettier --check .",
"build:cleanup": "rm -rf ./dist" "build:cleanup": "rm -rf ./dist"
} }
} }

View File

@@ -1,8 +1,8 @@
import path from 'node:path' import path from 'node:path'
import { envParser } from '@aklive2d/libs' import { envParser } from '@aklive2d/libs'
import config from '@aklive2d/config' import config from '@aklive2d/config'
import build from './libs/build.js' import build from './libs/build.ts'
import download from './libs/download.js' import download from './libs/download.ts'
const packageDir = path.resolve(import.meta.dirname, '..') const packageDir = path.resolve(import.meta.dirname, '..')
const dataDir = path.resolve(import.meta.dirname, config.dir_name.data) const dataDir = path.resolve(import.meta.dirname, config.dir_name.data)

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/**/*"]
}

35
packages/assets/types.ts Normal file
View File

@@ -0,0 +1,35 @@
export type Config = {
dynchars: string
item_to_download: ItemToDownload[]
additional_regex: string[]
}
export type ItemToDownload = string
export type UpdateList = {
versionId: string
abInfos: AbInfosItem[]
manifestName: string
manifestVersion: string
packInfos: PackInfosItem[]
}
export type AbInfosItem = {
name: string
hash: string
md5: string
totalSize: number
abSize: number
cid: number
cat?: number
pid?: string
}
export type PackInfosItem = {
name: string
hash: string
md5: string
totalSize: number
abSize: number
cid: number
}

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} */ /** @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

@@ -66,12 +66,18 @@ export const build = async () => {
return err return err
} }
const composite = async (filenamePrefix, fileExt) => { const composite = async (filenamePrefix: string, fileExt: string) => {
const image = sharp( const image = sharp(
path.join(EXTRACTED_DIR, `${filenamePrefix}_left${fileExt}`) path.join(EXTRACTED_DIR, `${filenamePrefix}_left${fileExt}`)
) )
const metadata = await image.metadata() const metadata = await image.metadata()
if (!metadata.width || !metadata.height) {
throw new Error(
`Width and height metadata for ${filenamePrefix}_left${fileExt} is not found.`
)
}
image image
.resize(2 * metadata.width, metadata.height, { .resize(2 * metadata.width, metadata.height, {
kernel: sharp.kernel.nearest, kernel: sharp.kernel.nearest,

View File

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

View File

@@ -1,4 +1,4 @@
import { build } from './index.js' import { build } from './index.ts'
import { envParser, error } from '@aklive2d/libs' import { envParser, error } from '@aklive2d/libs'
async function main() { async function main() {

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

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

View File

@@ -2,7 +2,7 @@
"name": "@aklive2d/charword-table", "name": "@aklive2d/charword-table",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"main": "index.js", "main": "index.ts",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@aklive2d/libs": "workspace:*", "@aklive2d/libs": "workspace:*",
@@ -12,10 +12,15 @@
"@aklive2d/eslint-config": "workspace:*", "@aklive2d/eslint-config": "workspace:*",
"@aklive2d/prettier-config": "workspace:*" "@aklive2d/prettier-config": "workspace:*"
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"scripts": { "scripts": {
"update": "mode=update node runner.js", "update": "mode=update bun runner.ts",
"build": "mode=build node runner.js", "build": "mode=build bun runner.ts",
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check .", "lint": "eslint && prettier --check .",
"build:cleanup": "rm -rf ./dist" "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' import { envParser, error } from '@aklive2d/libs'
async function main() { async function main() {
let err = [] let err: string[] = []
const { mode, name } = envParser.parse({ const { mode, name } = envParser.parse({
mode: { mode: {
type: 'string', type: 'string',
@@ -17,7 +17,7 @@ async function main() {
}) })
switch (mode) { switch (mode) {
case 'build': case 'build':
err = await build(name) err = await build(name as string[])
break break
case 'update': case 'update':
await 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
}
}

View File

@@ -49,6 +49,7 @@ app:
showcase: showcase:
public: public public: public
assets: assets assets: assets
release: release
dir_name: dir_name:
data: data data: data
dist: dist dist: dist

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} */ /** @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

@@ -1,4 +0,0 @@
import path from 'node:path'
import { yaml } from '@aklive2d/libs'
export default yaml.read(path.resolve(import.meta.dirname, 'config.yaml'))

9
packages/config/index.ts Normal file
View File

@@ -0,0 +1,9 @@
import path from 'node:path'
import { yaml } from '@aklive2d/libs'
import type { Config } from './types'
const config: Config = yaml.read(
path.resolve(import.meta.dirname, 'config.yaml')
)
export default config

View File

@@ -1,7 +1,7 @@
{ {
"name": "@aklive2d/config", "name": "@aklive2d/config",
"version": "0.0.0", "version": "0.0.0",
"main": "index.js", "main": "index.ts",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -9,7 +9,12 @@
"@aklive2d/eslint-config": "workspace:*", "@aklive2d/eslint-config": "workspace:*",
"@aklive2d/prettier-config": "workspace:*" "@aklive2d/prettier-config": "workspace:*"
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"scripts": { "scripts": {
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check ." "lint": "eslint && prettier --check ."
} }
} }

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/**/*"]
}

103
packages/config/types.ts Normal file
View File

@@ -0,0 +1,103 @@
export type Config = {
site_id: string
total_size: number
akassets: {
project_name: string
url: string
}
insight: {
id: string
url: string
}
module: {
assets: {
config_yaml: string
background: string
music: string
charword_table: string
project_json: string
}
background: {
operator_bg_png: string
}
charword_table: {
charword_table_json: string
}
music: {
music_table_json: string
display_meta_table_json: string
audio_data_json: string
}
official_info: {
official_info_json: string
}
operator: {
operator: string
config: string
template_yaml: string
config_yaml: string
portraits: string
logos_assets: string
logos: string
directory_assets: string
MonoBehaviour: string
Texture2D: string
title: {
'zh-CN': string
'en-US': string
}
}
project_json: {
project_json: string
preview_jpg: string
template_yaml: string
}
wrangler: {
index_json: string
}
vite_helpers: {
config_json: string
}
}
app: {
showcase: {
public: string
assets: string
release: string
}
}
dir_name: {
data: string
dist: string
extracted: string
auto_update: string
voice: {
main: string
sub: {
name: string
lang: string
lookup_region: string
}[]
}
}
directory: {
assets_dir: string
title: string
voice: string
error: {
files: {
key: string
paddings: {
left: string
right: string
top: string
bottom: string
}
}[]
voice: {
file: string
target: string
}
}
}
}

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} */ /** @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

@@ -5,7 +5,16 @@ import { pipeline } from 'node:stream/promises'
import pThrottle from 'p-throttle' import pThrottle from 'p-throttle'
import { file as fileLib } from '@aklive2d/libs' import { file as fileLib } from '@aklive2d/libs'
export const githubDownload = async (history_url, raw_url, filepath) => { export type UnzipDownloadItem = {
name: string
url: string
}
export const githubDownload = async (
history_url: string,
raw_url: string,
filepath: string
) => {
const historyResponse = await fetch(history_url) const historyResponse = await fetch(history_url)
const historyData = await historyResponse.json() const historyData = await historyResponse.json()
const lastCommit = historyData[0] const lastCommit = historyData[0]
@@ -21,7 +30,8 @@ export const githubDownload = async (history_url, raw_url, filepath) => {
if (fileLib.exists(filepath)) { if (fileLib.exists(filepath)) {
console.log(`${basename} is the latest version.`) console.log(`${basename} is the latest version.`)
return JSON.parse(fileLib.readSync(filepath)) const content = fileLib.readSync(filepath)
return content ? JSON.parse(content) : null
} }
const response = await fetch(raw_url) const response = await fetch(raw_url)
const data = await response.json() const data = await response.json()
@@ -39,9 +49,12 @@ export const githubDownload = async (history_url, raw_url, filepath) => {
} }
export const unzipDownload = async ( export const unzipDownload = async (
filesToDownload, filesToDownload: UnzipDownloadItem[],
targetDir, targetDir: string,
opts = { opts: {
defaultRegex: RegExp | null
matchRegExps: RegExp[]
} = {
defaultRegex: null, defaultRegex: null,
matchRegExps: [], matchRegExps: [],
} }
@@ -52,7 +65,7 @@ export const unzipDownload = async (
interval: 1000, interval: 1000,
}) })
while (retry.length > 0) { while (retry.length > 0) {
const newRetry = [] const newRetry: UnzipDownloadItem[] = []
await Promise.all( await Promise.all(
retry.map( retry.map(
throttle(async (item) => { throttle(async (item) => {
@@ -72,7 +85,9 @@ export const unzipDownload = async (
}) })
.catch((err) => { .catch((err) => {
console.error(err) console.error(err)
return null
}) })
if (!zip) return
try { try {
for await (const entry of zip) { for await (const entry of zip) {
if ( if (

View File

@@ -2,7 +2,7 @@
"name": "@aklive2d/downloader", "name": "@aklive2d/downloader",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"main": "index.js", "main": "index.ts",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@aklive2d/eslint-config": "workspace:*", "@aklive2d/eslint-config": "workspace:*",
@@ -11,6 +11,6 @@
"p-throttle": "^7.0.0" "p-throttle": "^7.0.0"
}, },
"scripts": { "scripts": {
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check ." "lint": "eslint && prettier --check ."
} }
} }

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

@@ -1,6 +1,7 @@
import js from '@eslint/js' import js from '@eslint/js'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended' import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
import globals from 'globals' import globals from 'globals'
import tsConfig from './ts.js'
export default [ export default [
{ ignores: ['dist'] }, { ignores: ['dist'] },
@@ -24,3 +25,5 @@ export default [
}, },
eslintPluginPrettierRecommended, eslintPluginPrettierRecommended,
] ]
export { tsConfig }

View File

@@ -0,0 +1,4 @@
import eslint from '@eslint/js'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
export default [eslint.configs.recommended, eslintPluginPrettierRecommended]

View File

@@ -1,17 +1,16 @@
{ {
"name": "@aklive2d/eslint-config", "name": "@aklive2d/eslint-config",
"version": "0.0.0", "version": "0.0.0",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint/js": "^9.19.0", "@eslint/js": "^9.19.0",
"eslint": "^9.19.0", "eslint": "^9.19.0",
"eslint-config-prettier": "^10.0.1", "eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.3", "eslint-plugin-prettier": "^5.2.6",
"globals": "^15.14.0" "globals": "^16.0.0",
}, "typescript": "^5.8.3",
"publishConfig": { "typescript-eslint": "^8.31.1"
"access": "public" }
}
} }

View File

@@ -0,0 +1,10 @@
import eslint from '@eslint/js'
import tseslint from 'typescript-eslint'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
/** @type {import('eslint').Config} */
export default tseslint.config(
eslint.configs.recommended,
tseslint.configs.recommended,
eslintPluginPrettierRecommended
)

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} */ /** @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

@@ -1,13 +0,0 @@
import * as file from './libs/file.js'
import * as yaml from './libs/yaml.js'
import * as env from './libs/env.js'
import * as error from './libs/error.js'
import * as alphaComposite from './libs/alpha_composite.js'
import * as envParser from './libs/env_parser.js'
export { file }
export { yaml }
export { env }
export { error }
export { alphaComposite }
export { envParser }

13
packages/libs/index.ts Normal file
View File

@@ -0,0 +1,13 @@
import * as file from './libs/file.ts'
import * as yaml from './libs/yaml.ts'
import * as env from './libs/env.ts'
import * as error from './libs/error.ts'
import * as alphaComposite from './libs/alpha_composite.ts'
import * as envParser from './libs/env_parser.ts'
export { file }
export { yaml }
export { env }
export { error }
export { alphaComposite }
export { envParser }

View File

@@ -1,7 +1,19 @@
import sharp from 'sharp' import sharp from 'sharp'
import path from 'node:path' import path from 'node:path'
export const process = async (filename, maskFilename, extractedDir) => { export type Rect = {
x: number
y: number
w: number
h: number
rotate: number
}
export const process = async (
filename: string,
maskFilename: string,
extractedDir: string
) => {
const image = sharp(path.join(extractedDir, filename)).removeAlpha() const image = sharp(path.join(extractedDir, filename)).removeAlpha()
const imageMeta = await image.metadata() const imageMeta = await image.metadata()
const imageBuffer = await image.toBuffer() const imageBuffer = await image.toBuffer()
@@ -13,7 +25,22 @@ export const process = async (filename, maskFilename, extractedDir) => {
return sharp(imageBuffer).joinChannel(mask).toBuffer() return sharp(imageBuffer).joinChannel(mask).toBuffer()
} }
export const crop = async (buffer, rect) => { export const crop = async (
buffer:
| Buffer
| ArrayBuffer
| Uint8Array
| Uint8ClampedArray
| Int8Array
| Uint16Array
| Int16Array
| Uint32Array
| Int32Array
| Float32Array
| Float64Array
| string,
rect: Rect
) => {
const left = rect.y const left = rect.y
const top = rect.x const top = rect.x
const width = rect.h const width = rect.h
@@ -28,7 +55,7 @@ export const crop = async (buffer, rect) => {
return await sharp(newImage).rotate(rotate).toBuffer() return await sharp(newImage).rotate(rotate).toBuffer()
} }
export const toBuffer = async (filename, extractedDir) => { export const toBuffer = async (filename: string, extractedDir: string) => {
const file = path.join(extractedDir, filename) const file = path.join(extractedDir, filename)
const { data, info } = await sharp(file) const { data, info } = await sharp(file)
.raw() .raw()
@@ -36,7 +63,7 @@ export const toBuffer = async (filename, extractedDir) => {
const { width, height, channels } = info const { width, height, channels } = info
const pixelArray = new Uint8ClampedArray(data.buffer) const pixelArray = new Uint8ClampedArray(data.buffer)
for (let i = 0; i < pixelArray.length; i += 4) { for (let i = 0; i < pixelArray.length; i += 4) {
let alpha = pixelArray[i + 3] / 255 const alpha = pixelArray[i + 3] / 255
pixelArray[i + 0] = pixelArray[i + 0] * alpha pixelArray[i + 0] = pixelArray[i + 0] * alpha
pixelArray[i + 1] = pixelArray[i + 1] * alpha pixelArray[i + 1] = pixelArray[i + 1] * alpha
pixelArray[i + 2] = pixelArray[i + 2] * alpha pixelArray[i + 2] = pixelArray[i + 2] * alpha

View File

@@ -1,4 +1,9 @@
export function generate(values) { export function generate(
values: {
key: string
value: string
}[]
) {
return values return values
.map((value) => { .map((value) => {
return `VITE_${value.key.toUpperCase()}=${value.value}` return `VITE_${value.key.toUpperCase()}=${value.value}`

View File

@@ -1,28 +1,40 @@
import process from 'node:process' import process from 'node:process'
export const parse = (args) => { type Args = {
[name: string]: {
type?: string
default?: unknown
multiple?: boolean
short?: string
}
}
export const parse = (args: Args) => {
const envVars = process.env const envVars = process.env
const argKeys = Object.keys(args) const argKeys = Object.keys(args)
const values = {} const values: Record<string, unknown | unknown[]> = {}
argKeys.map((key) => { argKeys.map((key) => {
let noInput = false let noInput = false
let value, let value: unknown
type = args[key].type || 'string', const type = args[key].type || 'string',
defaultVal = args[key].default, defaultVal = args[key].default,
multiple = args[key].multiple || false, multiple = args[key].multiple || false,
short = args[key].short short = args[key].short
value = envVars[key] || envVars[short] value = short
? envVars[short]
? envVars[short]
: envVars[key]
: envVars[key]
if (!value) noInput = true if (!value) noInput = true
value = noInput ? defaultVal : value value = noInput ? defaultVal : value
if (noInput) { if (noInput) {
values[key] = value values[key] = value
} else { } else {
value = multiple ? value.split(',') : value const cValue = multiple ? (value as string).split(',') : value
if (multiple) { if (multiple) {
values[key] = [] values[key] = (cValue as string[]).map((item: string) =>
value.map((item) => { typeCast(type, item)
values[key].push(typeCast(type, item)) )
})
} else { } else {
values[key] = typeCast(type, value) values[key] = typeCast(type, value)
} }
@@ -31,7 +43,7 @@ export const parse = (args) => {
return values return values
} }
const typeCast = (type, value) => { const typeCast = (type: 'number' | 'boolean' | string, value: unknown) => {
switch (type) { switch (type) {
case 'number': case 'number':
return Number(value) return Number(value)

View File

@@ -1,4 +1,4 @@
export const handle = (err) => { export const handle = (err: string[]) => {
if (err.length > 0) { if (err.length > 0) {
const str = `${err.length} error${err.length > 1 ? 's were' : ' was'} found:\n${err.join('\n')}` const str = `${err.length} error${err.length > 1 ? 's were' : ' was'} found:\n${err.join('\n')}`
throw new Error(str) throw new Error(str)

View File

@@ -3,52 +3,61 @@ import path from 'node:path'
import yauzl from 'yauzl-promise' import yauzl from 'yauzl-promise'
import yazl from 'yazl' import yazl from 'yazl'
export async function write(content, filePath) { export async function write(
content: string | NodeJS.ArrayBufferView,
filePath: string
) {
mkdir(path.dirname(filePath)) mkdir(path.dirname(filePath))
return await fsP.writeFile(filePath, content, { flag: 'w' }) return await fsP.writeFile(filePath, content, { flag: 'w' })
} }
export function writeSync(content, filePath) { export function writeSync(
content: string | NodeJS.ArrayBufferView,
filePath: string
) {
mkdir(path.dirname(filePath)) mkdir(path.dirname(filePath))
return fs.writeFileSync(filePath, content, { flag: 'w' }) return fs.writeFileSync(filePath, content, { flag: 'w' })
} }
export async function read(filePath, encoding = 'utf8') { export async function read(
return await fsP.readFile(filePath, encoding, { flag: 'r' }) filePath: string,
encoding: BufferEncoding = 'utf8'
) {
return await fsP.readFile(filePath, { encoding, flag: 'r' })
} }
export function readSync(filePath, encoding = 'utf8') { export function readSync(filePath: string, encoding: BufferEncoding = 'utf8') {
if (exists(filePath)) { if (exists(filePath)) {
return fs.readFileSync(filePath, encoding, { flag: 'r' }) return fs.readFileSync(filePath, { encoding, flag: 'r' })
} }
return null return null
} }
export function exists(filePath) { export function exists(filePath: string) {
return fs.existsSync(filePath) return fs.existsSync(filePath)
} }
export function rmdir(dir) { export function rmdir(dir: string) {
if (exists(dir)) { if (exists(dir)) {
fs.rmSync(dir, { recursive: true }) fs.rmSync(dir, { recursive: true })
} }
} }
export function rm(dir) { export function rm(dir: string) {
if (exists(dir)) { if (exists(dir)) {
fs.rmSync(dir, { recursive: true }) fs.rmSync(dir, { recursive: true })
} }
} }
export function mkdir(dir) { export function mkdir(dir: string) {
if (!exists(dir)) { if (!exists(dir)) {
fs.mkdirSync(dir, { recursive: true }) fs.mkdirSync(dir, { recursive: true })
} }
} }
export async function copy( export async function copy(
sourcePath, sourcePath: string,
targetPath, targetPath: string,
mode = fs.constants.COPYFILE_FICLONE mode = fs.constants.COPYFILE_FICLONE
) { ) {
if (!exists(sourcePath)) { if (!exists(sourcePath)) {
@@ -60,8 +69,8 @@ export async function copy(
} }
export async function copyDir( export async function copyDir(
sourcePath, sourcePath: string,
targetPath, targetPath: string,
mode = fs.constants.COPYFILE_FICLONE mode = fs.constants.COPYFILE_FICLONE
) { ) {
if (!exists(sourcePath)) { if (!exists(sourcePath)) {
@@ -72,11 +81,11 @@ export async function copyDir(
return await fsP.cp(sourcePath, targetPath, { recursive: true, mode }) return await fsP.cp(sourcePath, targetPath, { recursive: true, mode })
} }
export function appendSync(content, filePath) { export function appendSync(content: string | Uint8Array, filePath: string) {
return fs.appendFileSync(filePath, content, 'utf8') return fs.appendFileSync(filePath, content, 'utf8')
} }
export function readdirSync(dir) { export function readdirSync(dir: string) {
if (!exists(dir)) { if (!exists(dir)) {
console.warn(`Source ${dir} does not exist.`) console.warn(`Source ${dir} does not exist.`)
return [] return []
@@ -84,7 +93,7 @@ export function readdirSync(dir) {
return fs.readdirSync(dir) return fs.readdirSync(dir)
} }
export function fileTypeSync(dir) { export function fileTypeSync(dir: string) {
if (!exists(dir)) { if (!exists(dir)) {
console.warn(`Source ${dir} does not exist.`) console.warn(`Source ${dir} does not exist.`)
return null return null
@@ -92,7 +101,7 @@ export function fileTypeSync(dir) {
return fs.statSync(dir).isDirectory() ? 'dir' : 'file' return fs.statSync(dir).isDirectory() ? 'dir' : 'file'
} }
export const symlink = (source, target) => { export const symlink = (source: string, target: string) => {
if (!exists(source)) { if (!exists(source)) {
console.warn(`Source ${source} does not exist.`) console.warn(`Source ${source} does not exist.`)
return return
@@ -105,14 +114,14 @@ export const symlink = (source, target) => {
fs.symlinkSync(relative, target) fs.symlinkSync(relative, target)
} }
export const symlinkAll = (source, target) => { export const symlinkAll = (source: string, target: string) => {
const files = readdirSync(source) const files = readdirSync(source)
files.map((file) => { files.map((file) => {
symlink(path.join(source, file), path.join(target, file)) symlink(path.join(source, file), path.join(target, file))
}) })
} }
export const mv = (source, target) => { export const mv = (source: string, target: string) => {
if (!exists(source)) { if (!exists(source)) {
console.warn(`Source file ${source} does not exist.`) console.warn(`Source file ${source} does not exist.`)
return return
@@ -125,8 +134,8 @@ export const mv = (source, target) => {
} }
export const cpSync = ( export const cpSync = (
source, source: string,
target, target: string,
opts = { opts = {
dereference: false, dereference: false,
} }
@@ -146,7 +155,7 @@ export const cpSync = (
}) })
} }
export const relative = (source, target) => { export const relative = (source: string, target: string) => {
if (!exists(source)) { if (!exists(source)) {
console.warn(`Source file ${source} does not exist.`) console.warn(`Source file ${source} does not exist.`)
return return
@@ -154,7 +163,7 @@ export const relative = (source, target) => {
return path.relative(source, target) return path.relative(source, target)
} }
export const size = (source) => { export const size = (source: string) => {
if (!exists(source)) { if (!exists(source)) {
console.warn(`Source file ${source} does not exist.`) console.warn(`Source file ${source} does not exist.`)
return return

View File

@@ -1,12 +1,17 @@
import fs from 'node:fs' import fs from 'node:fs'
import path from 'node:path' import path from 'node:path'
import { parse } from 'yaml' import { parse } from 'yaml'
import type { Tags, ScalarTag, SchemaOptions, CollectionTag } from 'yaml'
export function read(file_dir, customTags = []) { export function read(
const include = { file_dir: string,
identify: (value) => value.startsWith('!include'), customTags: ScalarTag[] | CollectionTag[] = []
) {
const include: ScalarTag = {
identify: (value: unknown) =>
typeof value === 'string' && value.startsWith('!include'),
tag: '!include', tag: '!include',
resolve(str) { resolve(str: string) {
const dir = path.resolve(path.dirname(file_dir), str) const dir = path.resolve(path.dirname(file_dir), str)
const data = read(dir) const data = read(dir)
return data return data
@@ -15,5 +20,5 @@ export function read(file_dir, customTags = []) {
const file = fs.readFileSync(file_dir, 'utf8') const file = fs.readFileSync(file_dir, 'utf8')
return parse(file, { return parse(file, {
customTags: [include, ...customTags], customTags: [include, ...customTags],
}) } as SchemaOptions)
} }

View File

@@ -2,7 +2,7 @@
"name": "@aklive2d/libs", "name": "@aklive2d/libs",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"main": "index.js", "main": "index.ts",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@aklive2d/eslint-config": "workspace:*", "@aklive2d/eslint-config": "workspace:*",
@@ -12,7 +12,16 @@
"yauzl-promise": "^4.0.0", "yauzl-promise": "^4.0.0",
"yazl": "^3.3.1" "yazl": "^3.3.1"
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"scripts": { "scripts": {
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check ." "lint": "eslint && prettier --check ."
},
"devDependencies": {
"@types/yauzl-promise": "^4.0.1",
"@types/yazl": "^2.4.6"
} }
} }

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": ["libs/**/*"],
"exclude": ["dist/**/*", "data/**/*"]
}

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} */ /** @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

@@ -2,6 +2,16 @@ import path from 'node:path'
import { file } from '@aklive2d/libs' import { file } from '@aklive2d/libs'
import { githubDownload } from '@aklive2d/downloader' import { githubDownload } from '@aklive2d/downloader'
import config from '@aklive2d/config' import config from '@aklive2d/config'
import type {
DisplayMetaTable,
AudioDataTable,
MusicTable,
MusicFileMapping,
MusicFiles,
MusicItem,
MusicDataItem,
MusicMapping,
} from './types.ts'
const AUTO_UPDATE_FOLDER = path.resolve( const AUTO_UPDATE_FOLDER = path.resolve(
import.meta.dirname, import.meta.dirname,
@@ -22,12 +32,12 @@ const download = async () => {
AUTO_UPDATE_FOLDER, AUTO_UPDATE_FOLDER,
config.module.music.audio_data_json config.module.music.audio_data_json
) )
const metaTable = await githubDownload( const metaTable: DisplayMetaTable = await githubDownload(
`https://api.github.com/repos/Kengxxiao/ArknightsGameData/commits?path=zh_CN/gamedata/excel/display_meta_table.json`, `https://api.github.com/repos/Kengxxiao/ArknightsGameData/commits?path=zh_CN/gamedata/excel/display_meta_table.json`,
`https://raw.githubusercontent.com/Kengxxiao/ArknightsGameData/master/zh_CN/gamedata/excel/display_meta_table.json`, `https://raw.githubusercontent.com/Kengxxiao/ArknightsGameData/master/zh_CN/gamedata/excel/display_meta_table.json`,
display_meta_table_json display_meta_table_json
) )
const audioDataTable = await githubDownload( const audioDataTable: AudioDataTable = await githubDownload(
`https://api.github.com/repos/Kengxxiao/ArknightsGameData/commits?path=zh_CN/gamedata/excel/audio_data.json`, `https://api.github.com/repos/Kengxxiao/ArknightsGameData/commits?path=zh_CN/gamedata/excel/audio_data.json`,
`https://raw.githubusercontent.com/Kengxxiao/ArknightsGameData/master/zh_CN/gamedata/excel/audio_data.json`, `https://raw.githubusercontent.com/Kengxxiao/ArknightsGameData/master/zh_CN/gamedata/excel/audio_data.json`,
audio_data_json audio_data_json
@@ -40,14 +50,17 @@ const download = async () => {
const generateMapping = () => { const generateMapping = () => {
const musicFolder = DATA_DIR const musicFolder = DATA_DIR
const musicTable = JSON.parse(file.readSync(MUSIC_TABLE_JSON)) const musicTableContent = file.readSync(MUSIC_TABLE_JSON)
const musicFileMapping = {} const musicTable: MusicTable = musicTableContent
const musicFiles = [] ? JSON.parse(musicTableContent)
: null
const musicFileMapping: MusicFileMapping = {}
const musicFiles: MusicFiles = []
if (!musicTable) return if (!musicTable) throw new Error('Music table not found')
for (const item of musicTable) { for (const item of musicTable) {
const key = `${item.id}.png` const key = `${item.id}.png`
musicFileMapping[key] = {} musicFileMapping[key] = {} as MusicItem
if (item.intro) { if (item.intro) {
const filename = `${item.intro.split('/').pop()}.ogg` const filename = `${item.intro.split('/').pop()}.ogg`
@@ -78,34 +91,37 @@ const generateMapping = () => {
} }
} }
export const mapping = generateMapping() export const mapping: MusicMapping = generateMapping()
export const update = async () => { export const update = async () => {
const { metaTable, audioDataTable } = await download() const { metaTable, audioDataTable } = await download()
const musicTable = audioDataTable.musics const musicTable = audioDataTable.musics
const musicBank = audioDataTable.bgmBanks const musicBank = audioDataTable.bgmBanks
const musicBankAlias = audioDataTable.bankAlias const musicBankAlias = audioDataTable.bankAlias
const musicData = metaTable.homeBackgroundData.homeBgDataList.reduce( const musicData: MusicDataItem[] =
(acc, cur) => { metaTable.homeBackgroundData.homeBgDataList.reduce((acc, cur) => {
acc.push({ acc.push({
id: cur.bgId, id: cur.bgId,
musicId: cur.bgMusicId, musicId: cur.bgMusicId,
}) })
return acc return acc
}, }, [] as MusicDataItem[])
[]
)
const list = [] const list = []
for (const item of musicData) { for (const item of musicData) {
let bankName = musicTable.find((el) => item.musicId === el.id).bank const bankItem = musicTable.find((el) => item.musicId === el.id)
if (typeof bankItem === 'undefined')
console.warn(`No music found for id ${item.musicId}`)
let bankName = bankItem!.bank
if (typeof musicBankAlias[bankName] !== 'undefined') { if (typeof musicBankAlias[bankName] !== 'undefined') {
bankName = musicBankAlias[bankName] bankName = musicBankAlias[bankName]
} }
const obj = musicBank.find((el) => bankName === el.name) const obj = musicBank.find((el) => bankName === el.name)
if (typeof obj === 'undefined')
console.warn(`No bank found for name ${bankName}`)
list.push({ list.push({
id: item.id, id: item.id,
intro: obj.intro, intro: obj!.intro,
loop: obj.loop, loop: obj!.loop,
}) })
} }
list.push({ list.push({

View File

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

View File

@@ -1,5 +1,5 @@
import { envParser } from '@aklive2d/libs' import { envParser } from '@aklive2d/libs'
import { update } from './index.js' import { update } from './index.ts'
async function main() { async function main() {
const { mode } = envParser.parse({ const { mode } = envParser.parse({

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/**/*"]
}

295
packages/music/types.ts Normal file
View File

@@ -0,0 +1,295 @@
export type MusicMapping = {
musicFiles: MusicFiles
musicFileMapping: MusicFileMapping
}
export type MusicItem = {
id?: string
intro: string | null
loop: string | null
}
export type MusicTable = MusicItem[]
export type MusicFileMapping = Record<string, MusicItem>
export type MusicFile = {
filename: string
source: string
}
export type MusicFiles = MusicFile[]
export type DisplayMetaTable = {
playerAvatarData: {
defaultAvatarId: string
avatarList: {
avatarId: string
avatarType: string
avatarIdSort: number
avatarIdDesc: string
avatarItemName: string
avatarItemDesc: string
avatarItemUsage: string
obtainApproach: string
dynAvatarId: null | unknown
}[]
avatarTypeData: {
[key: string]: {
avatarType: string
typeName: string
sortId: number
avatarIdList: string[]
}
}
}
homeBackgroundData: {
defaultBackgroundId: string
defaultThemeId: string
homeBgDataList: {
bgId: string
bgSortId: number
bgStartTime: number
bgName: string
bgMusicId: string
bgDes: string
bgUsage: string
obtainApproach: string
unlockDesList: string[]
}[]
themeList: {
id: string
type: string
sortId: number
startTime: number
tmName: string
tmDes: string
tmUsage: string
obtainApproach: string
unlockDesList: string[]
isLimitObtain: boolean
hideWhenLimit: boolean
rarity: string
}[]
backgroundLimitData: {
[key: string]: {
bgId: string
limitInfos: {
limitInfoId: string
startTime: Date
endTime: Date
invalidObtainDesc: string
displayAfterEndTime: boolean
}[]
}
}
themeLimitData: {
[key: string]: {
id: string
limitInfos: {
startTime: Date
endTime: Date
invalidObtainDesc: string
}[]
}
}
defaultBgMusicId: string
themeStartTime: Date
}
nameCardV2Data: {
fixedModuleData: {
[key: string]: {
id: string
type: string
}
}
removableModuleData: {
[key: string]: {
sortId: number
subType: string
name: string
id: string
type: string
}
}
skinData: {
[key: string]: {
id: string
name: string
type: string
sortId: number
isSpTheme: boolean
defaultShowDetail: boolean
themeName: string
themeEnName: string
skinStartTime: number
skinDesc: string
usageDesc: string
skinApproach: string
unlockConditionCnt: number
unlockDescList: unknown[]
fixedModuleList: string[]
rarity: string
skinTmplCnt: number
canChangeTmpl: boolean
isTimeLimit: boolean
timeLimitInfoList: unknown[]
}
}
consts: {
defaultNameCardSkinId: string
canUidHide: boolean
removableModuleMaxCount: number
}
}
mailArchiveData: {
mailArchiveInfoDict: {
[key: string]: {
id: string
type: string
sortId: number
displayReceiveTs: number
year: number
dateDelta: number
senderId: string
title: string
content: string
rewardList: {
id: string
count: number
type: string
}[]
}
}
constData: {
funcOpenTs: number
}
}
mailSenderData: {
senderDict: {
[key: string]: {
senderId: string
senderName: string
avatarId: string
}
}
}
emoticonData: {
emojiDataDict: {
[key: string]: {
id: string
type: string
sortId: number
picId: string
desc: string
}
}
emoticonThemeDataDict: { [key: string]: string[] }
}
storyVariantData: {
[key: string]: {
plotTaskId: string
spStoryId: string
storyId: string
priority: number
startTime: number
endTime: number
template: string
param: string[]
}
}
}
export type AudioDataTable = {
bgmBanks: {
intro: string
loop: string
volume: number
crossfade: number
delay: number
fadeStyleId: null | string
name: string
}[]
soundFXBanks: {
sounds: {
asset: string
weight: number
important: boolean
is2D: boolean
delay: number
minPitch: number
maxPitch: number
minVolume: number
maxVolume: number
ignoreTimeScale: boolean
}[]
maxSoundAllowed: number
popOldest: boolean
customMixerGroup: string
loop: boolean
name: string
mixerDesc: null | string
}[]
soundFXCtrlBanks: {
targetBank: string
ctrlStop: boolean
ctrlStopFadetime: number
name: string
}[]
snapshotBanks: {
targetSnapshot: string
hookSoundFxBank: string
delay: number
duration: number
name: string
}[]
battleVoice: {
crossfade: number
minTimeDeltaForEnemyEncounter: number
minSpCostForImportantPassiveSkill: number
voiceTypeOptions: {
voiceType: string
priority: number
overlapIfSamePriority: boolean
cooldown: number
delay: number
}[]
}
musics: {
id: string
name: string
bank: string
}[]
duckings: {
bank: string
volume: number
fadeTime: number
delay: number
fadeStyleId: null | string
}[]
fadeStyles: {
styleName: string
fadeinTime: number
fadeoutTime: number
fadeinType: string
fadeoutType: string
}[]
soundFxVoiceLang: {
[key: string]: {
[key: string]: {
JP: string
CN_MANDARIN: string
KR: string
EN?: string
}
}
}
bankAlias: {
[key: string]: string
}
}
export type MusicDataItem = {
id: string
musicId: string
}

File diff suppressed because it is too large Load Diff

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} */ /** @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

@@ -2,6 +2,13 @@ import jsdom from 'jsdom'
import path from 'node:path' import path from 'node:path'
import { file } from '@aklive2d/libs' import { file } from '@aklive2d/libs'
import config from '@aklive2d/config' import config from '@aklive2d/config'
import type {
OfficialArray,
OfficialInfo,
OfficialOperatorInfo,
OfficialInfoMapping,
OfficialInfoOperatorConfig,
} from './types'
const AUTO_UPDATE_FOLDER = path.resolve( const AUTO_UPDATE_FOLDER = path.resolve(
import.meta.dirname, import.meta.dirname,
@@ -18,22 +25,24 @@ export const update = async () => {
const dom = new jsdom.JSDOM(html_text) const dom = new jsdom.JSDOM(html_text)
const scripts = dom.window.document.body.querySelectorAll('script') const scripts = dom.window.document.body.querySelectorAll('script')
let data let data: OfficialArray | null = null
scripts.forEach((e) => { scripts.forEach((e) => {
if (e.textContent.includes('干员晋升')) { if (e.textContent?.includes('干员晋升')) {
data = JSON.parse( data = JSON.parse(
e.textContent e.textContent
.replace('self.__next_f.push([1,"c:', '') .replace('self.__next_f.push([1,"c:', '')
.replace('\\n"])', '') .replace('\\n"])', '')
.replaceAll('\\', '') .replaceAll('\\', '')
) ) as OfficialArray
} }
}) })
const rows = data[0][3].initialData if (!data) throw new Error('No data found')
const rows = (data as OfficialArray)[0][3].initialData
const dict = { const dict: OfficialInfo = {
length: rows.length, length: rows.length,
dates: [], dates: [],
info: {},
} }
let current_displayTime = rows[0].displayTime let current_displayTime = rows[0].displayTime
@@ -42,22 +51,22 @@ export const update = async () => {
for (const row of rows) { for (const row of rows) {
const displayTime = row.displayTime const displayTime = row.displayTime
if (displayTime !== current_displayTime) { if (displayTime !== current_displayTime) {
dict[current_displayTime] = current_block dict.info[current_displayTime] = current_block
dict.dates.push(current_displayTime) dict.dates.push(current_displayTime)
current_displayTime = row.displayTime current_displayTime = row.displayTime
current_block = [] current_block = []
} }
current_block.push(get_row(row)) current_block.push(get_row(row))
} }
dict[current_displayTime] = current_block dict.info[current_displayTime] = current_block
dict.dates.push(current_displayTime) dict.dates.push(current_displayTime)
file.writeSync(JSON.stringify(dict, null, 4), OFFICIAL_INFO_JSON) file.writeSync(JSON.stringify(dict, null, 4), OFFICIAL_INFO_JSON)
} }
const get_row = (row) => { const get_row = (row: OfficialOperatorInfo): OfficialInfoOperatorConfig => {
const type = row.type const type = row.type
let codename_zhCN, item_type let codename_zhCN, item_type: 'operator' | 'skin'
switch (type) { switch (type) {
case 0: case 0:
codename_zhCN = row.charName codename_zhCN = row.charName
@@ -82,18 +91,18 @@ const get_row = (row) => {
} }
const generateMapping = () => { const generateMapping = () => {
const mapping = {} const mapping: OfficialInfoMapping = {}
const data = JSON.parse(file.readSync(OFFICIAL_INFO_JSON)) const content = file.readSync(OFFICIAL_INFO_JSON)
if (!data) return if (!content) throw new Error('Failed to read official info JSON')
Object.keys(data).forEach((key) => { const data: OfficialInfo = JSON.parse(content)
if (typeof data[key] === 'object') { if (!data) throw new Error('Failed to parse official info JSON')
data[key].forEach((operator) => { Object.keys(data.info).forEach((date) => {
mapping[operator.id] = { data.info[date].forEach((operator) => {
date: key, mapping[operator.id] = {
...operator, date,
} ...operator,
}) }
} })
}) })
return mapping return mapping
} }

View File

@@ -2,7 +2,7 @@
"name": "@aklive2d/official-info", "name": "@aklive2d/official-info",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"main": "index.js", "main": "index.ts",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"jsdom": "^26.0.0", "jsdom": "^26.0.0",
@@ -11,8 +11,13 @@
"@aklive2d/eslint-config": "workspace:*", "@aklive2d/eslint-config": "workspace:*",
"@aklive2d/prettier-config": "workspace:*" "@aklive2d/prettier-config": "workspace:*"
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"scripts": { "scripts": {
"update": "mode=update node runner.js", "update": "mode=update bun runner.ts",
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check ." "lint": "eslint && prettier --check ."
} }
} }

View File

@@ -1,5 +1,5 @@
import { envParser } from '@aklive2d/libs' import { envParser } from '@aklive2d/libs'
import { update } from './index.js' import { update } from './index.ts'
async function main() { async function main() {
const { mode } = envParser.parse({ const { mode } = envParser.parse({

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,48 @@
export type OfficialOperatorInfo = {
cid: string
charName: string
suitName: string
codename: string
type: number
displayTime: string
portraitSrc: string
}
export type OfficialDataArray = [
'$',
string,
null,
{
initialData: OfficialOperatorInfo[]
},
]
type UnrelatedDataArray = ['$', string, null, unknown]
export type OfficialArray = [OfficialDataArray, UnrelatedDataArray]
export type OfficialInfo = {
length: number
dates: string[]
info: {
[date: string]: OfficialInfoOperatorConfig[]
}
}
export interface OfficialInfoOperatorConfig {
codename: {
'zh-CN': string
'en-US': string
}
type: 'operator' | 'skin'
link: string
id: string
}
export interface OperatorConfig extends OfficialInfoOperatorConfig {
date: string
}
export type OfficialInfoMapping = {
[id: string]: OperatorConfig
}

View File

@@ -1,3 +1,5 @@
dist dist
data data
auto_update auto_update
config
config.yaml

View File

@@ -61,3 +61,7 @@ yu: !include config/yu.yaml
logos_radiant_serenity: !include config/logos_radiant_serenity.yaml logos_radiant_serenity: !include config/logos_radiant_serenity.yaml
muelsyse_golden_reverie: !include config/muelsyse_golden_reverie.yaml muelsyse_golden_reverie: !include config/muelsyse_golden_reverie.yaml
ines_melodic_flutter: !include config/ines_melodic_flutter.yaml ines_melodic_flutter: !include config/ines_melodic_flutter.yaml
wisadel_supernova: !include config/wisadel_supernova.yaml
archetto_glory_of_the_devout: !include config/archetto_glory_of_the_devout.yaml
kroos_moonlit_voyage: !include config/kroos_moonlit_voyage.yaml
exusiai_the_new_covenant: !include config/exusiai_the_new_covenant.yaml

View File

@@ -0,0 +1,13 @@
filename: dyn_illust_char_332_archet_sale#14
logo: logo_Laterano
fallback_name: char_332_archet_sale#14
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true
codename:
zh-CN: 至虔者荣光 · 空弦
en-US: Glory of the Devout / Archetto
use_json: false
official_id: '202504953'

View File

@@ -0,0 +1,13 @@
filename: dyn_illust_char_1041_angel2
logo: logo_penguin
fallback_name: char_1041_angel2_2
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true
codename:
zh-CN: 新约能天使
en-US: Exusiai the New Covenant
use_json: false
official_id: '202504941'

View File

@@ -0,0 +1,13 @@
filename: dyn_illust_char_124_kroos_sale#14
logo: logo_reserve1
fallback_name: char_124_kroos_sale#14
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true
codename:
zh-CN: 星月漂流记 · 克洛丝
en-US: Moonlit Voyage / Kroos
use_json: false
official_id: '202504992'

View File

@@ -0,0 +1,13 @@
filename: dyn_illust_char_1035_wisdel_sale#14
logo: logo_babel
fallback_name: char_1035_wisdel_sale#14
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true
codename:
zh-CN: 超新星 · 维什戴尔
en-US: Supernova / Wisadel
use_json: false
official_id: '202504974'

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} */ /** @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

@@ -3,19 +3,20 @@ import { stringify } from 'yaml'
import { yaml, file, alphaComposite } from '@aklive2d/libs' import { yaml, file, alphaComposite } from '@aklive2d/libs'
import config from '@aklive2d/config' import config from '@aklive2d/config'
import { mapping as officialInfoMapping } from '@aklive2d/official-info' import { mapping as officialInfoMapping } from '@aklive2d/official-info'
import type { Config, PortraitHub, PortraitJson, AssetsJson } from './types.ts'
export const CONFIG_PATH = path.resolve( export const CONFIG_PATH = path.resolve(
import.meta.dirname, import.meta.dirname,
config.module.operator.config_yaml config.module.operator.config_yaml
) )
const CONFIG = yaml.read(CONFIG_PATH) const CONFIG: Config = yaml.read(CONFIG_PATH)
export const OPERATOR_SOURCE_FOLDER = path.resolve( export const OPERATOR_SOURCE_FOLDER = path.resolve(
import.meta.dirname, import.meta.dirname,
config.dir_name.data config.dir_name.data
) )
const DIST_DIR = path.join(import.meta.dirname, config.dir_name.dist) const DIST_DIR = path.join(import.meta.dirname, config.dir_name.dist)
const getVoiceFolders = (name) => { const getVoiceFolders = (name: string) => {
return config.dir_name.voice.sub.map((sub) => return config.dir_name.voice.sub.map((sub) =>
path.join( path.join(
OPERATOR_SOURCE_FOLDER, OPERATOR_SOURCE_FOLDER,
@@ -26,7 +27,7 @@ const getVoiceFolders = (name) => {
) )
} }
const getExtractedFolder = (name) => { const getExtractedFolder = (name: string) => {
return path.join(OPERATOR_SOURCE_FOLDER, name, config.dir_name.extracted) return path.join(OPERATOR_SOURCE_FOLDER, name, config.dir_name.extracted)
} }
@@ -34,11 +35,11 @@ const getConfigFolder = () => {
return path.join(import.meta.dirname, config.module.operator.config) return path.join(import.meta.dirname, config.module.operator.config)
} }
const getDistFolder = (name) => { const getDistFolder = (name: string) => {
return path.join(DIST_DIR, config.module.operator.operator, name) return path.join(DIST_DIR, config.module.operator.operator, name)
} }
export const has = (name) => { export const has = (name: string) => {
return Object.keys(operators).includes(name) return Object.keys(operators).includes(name)
} }
@@ -67,7 +68,7 @@ const generateMapping = () => {
return CONFIG return CONFIG
} }
const copyVoices = (name) => { const copyVoices = (name: string) => {
file.symlinkAll( file.symlinkAll(
path.join(OPERATOR_SOURCE_FOLDER, name, config.dir_name.voice.main), path.join(OPERATOR_SOURCE_FOLDER, name, config.dir_name.voice.main),
path.join(DIST_DIR, config.dir_name.voice.main, name) path.join(DIST_DIR, config.dir_name.voice.main, name)
@@ -85,15 +86,15 @@ const operators = generateMapping()
export default operators export default operators
export function getOperatorId(name, matcher = '$2$3$4') { export function getOperatorId(name: string, matcher = '$2$3$4') {
return name.replace(/^(.*)(char_[\d]+)(_[A-Za-z0-9]+)(|_.*)$/g, matcher) return name.replace(/^(.*)(char_[\d]+)(_[A-Za-z0-9]+)(|_.*)$/g, matcher)
} }
export const getOperatorAlternativeId = (id) => { export const getOperatorAlternativeId = (id: string) => {
return getOperatorId(id, '$2$3') return getOperatorId(id, '$2$3')
} }
export const build = async (namesToBuild) => { export const build = async (namesToBuild: string[]) => {
const names = !namesToBuild.length ? Object.keys(operators) : namesToBuild const names = !namesToBuild.length ? Object.keys(operators) : namesToBuild
console.log('Generating assets for', names.length, 'operators') console.log('Generating assets for', names.length, 'operators')
for (const name of names) { for (const name of names) {
@@ -103,7 +104,7 @@ export const build = async (namesToBuild) => {
copyLogos() copyLogos()
} }
const generateAssets = async (name) => { const generateAssets = async (name: string) => {
const extractedDir = getExtractedFolder(name) const extractedDir = getExtractedFolder(name)
const outDir = getDistFolder(name) const outDir = getDistFolder(name)
file.rmdir(outDir) file.rmdir(outDir)
@@ -134,31 +135,35 @@ const generateAssets = async (name) => {
OPERATOR_SOURCE_FOLDER, OPERATOR_SOURCE_FOLDER,
config.module.operator.portraits config.module.operator.portraits
) )
const portraitHub = JSON.parse( const portraitHubContent = file.readSync(
file.readSync( path.join(
path.join( portraitDir,
portraitDir, config.module.operator.MonoBehaviour,
config.module.operator.MonoBehaviour, 'portrait_hub.json'
'portrait_hub.json'
)
) )
) )
if (!portraitHubContent) throw new Error('portrait_hub.json not found')
const portraitHub: PortraitHub = JSON.parse(portraitHubContent)
const fallback_name_lowerCase = fallback_name.toLowerCase() const fallback_name_lowerCase = fallback_name.toLowerCase()
const portraitAtlas = portraitHub._sprites.find( const portraitItem = portraitHub._sprites.find(
(item) => item.name.toLowerCase() === fallback_name_lowerCase (item) => item.name.toLowerCase() === fallback_name_lowerCase
).atlas )
const portraitJson = JSON.parse( if (!portraitItem) throw new Error(`portrait ${fallback_name} not found`)
file.readSync( const portraitAtlas = portraitItem.atlas
path.join( const portraitJsonText = file.readSync(
portraitDir, path.join(
config.module.operator.MonoBehaviour, portraitDir,
`portraits#${portraitAtlas}.json` config.module.operator.MonoBehaviour,
) `portraits#${portraitAtlas}.json`
) )
) )
if (!portraitJsonText)
throw new Error(`portrait ${fallback_name} json not found`)
const portraitJson: PortraitJson = JSON.parse(portraitJsonText)
const item = portraitJson._sprites.find( const item = portraitJson._sprites.find(
(item) => item.name.toLowerCase() === fallback_name_lowerCase (item) => item.name.toLowerCase() === fallback_name_lowerCase
) )
if (!item) throw new Error(`portrait ${fallback_name} not found`)
const rect = { const rect = {
...item.rect, ...item.rect,
rotate: item.rotate, rotate: item.rotate,
@@ -186,15 +191,18 @@ const generateAssets = async (name) => {
} }
export const generateAssetsJson = async ( export const generateAssetsJson = async (
filename, filename: string,
extractedDir, extractedDir: string,
targetDir, targetDir: string,
_opts = { _opts: {
useJSON?: boolean
useSymLink?: boolean
} = {
useJSON: false, useJSON: false,
useSymLink: true, useSymLink: true,
} }
) => { ) => {
const assetsJson = [] const assetsJson: AssetsJson = []
let skelFilename let skelFilename
if (_opts.useJSON) { if (_opts.useJSON) {
@@ -206,6 +214,8 @@ export const generateAssetsJson = async (
const atlasPath = path.join(extractedDir, atlasFilename) const atlasPath = path.join(extractedDir, atlasFilename)
let atlas = await file.read(atlasPath) let atlas = await file.read(atlasPath)
const matches = atlas.match(new RegExp(/(.*).png/g)) const matches = atlas.match(new RegExp(/(.*).png/g))
if (!matches)
throw new Error(`No matches found in atlas file ${atlasFilename}`)
for (const item of matches) { for (const item of matches) {
let buffer let buffer
const alphaCompositeFilename = `${path.parse(item).name}[alpha].png` const alphaCompositeFilename = `${path.parse(item).name}[alpha].png`
@@ -236,17 +246,19 @@ export const generateAssetsJson = async (
const dir = path.join(targetDir, item.filename) const dir = path.join(targetDir, item.filename)
if (item.content) { if (item.content) {
file.writeSync(item.content, dir) file.writeSync(item.content, dir)
} else { } else if (item.path) {
if (_opts.useSymLink) { if (_opts.useSymLink) {
file.symlink(item.path, dir) file.symlink(item.path, dir)
} else { } else {
file.cpSync(item.path, dir) file.cpSync(item.path, dir)
} }
} else {
throw new Error(`Invalid asset item: ${item}`)
} }
}) })
} }
export const init = (name, id) => { export const init = (name: string, id: string) => {
const voiceFolders = getVoiceFolders(name) const voiceFolders = getVoiceFolders(name)
const extractedFolder = getExtractedFolder(name) const extractedFolder = getExtractedFolder(name)
const operatorConfigFolder = getConfigFolder() const operatorConfigFolder = getConfigFolder()

View File

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

View File

@@ -1,5 +1,11 @@
import { envParser } from '@aklive2d/libs' import { envParser } from '@aklive2d/libs'
import { build, init } from './index.js' import { build, init } from './index.ts'
type Args = {
mode: string
name: string[]
id: string
}
async function main() { async function main() {
const { mode, name, id } = envParser.parse({ const { mode, name, id } = envParser.parse({
@@ -16,7 +22,7 @@ async function main() {
id: { id: {
type: 'string', type: 'string',
}, },
}) }) as Args
switch (mode) { switch (mode) {
case 'build': case 'build':
await build(name) await build(name)

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,93 @@
export type Codename = { 'zh-CN': string; 'en-US': string }
export interface OperatorConfig {
filename: string
logo: string
fallback_name: string
viewport_left: number
viewport_right: number
viewport_top: number
viewport_bottom: number
invert_filter: boolean
codename: Codename
use_json: boolean
official_id: string
title: string
type: 'operator' | 'skin'
link: string
id: string
date: string
}
export type Config = {
[name: string]: OperatorConfig
}
type FileIDPathID = {
m_FileID: number
m_PathID: number
}
export type PortraitHub = {
m_GameObject: FileIDPathID
m_Enabled: number
m_Script: FileIDPathID
m_Name: string
_sprites: {
name: string
atlas: number
}[]
_atlases: string[]
_inputSpriteDir: string
_outputAtlasDir: string
_rootAtlasName: string
_spriteSize: {
width: number
height: number
}
_cntPerAtlas: number
_maxAtlasSize: number
}
type SignedItem = {
name: string
guid: string
md5: string
}
export type PortraitJson = {
m_GameObject: FileIDPathID
m_Enabled: number
m_Script: FileIDPathID
m_Name: string
_sprites: {
name: string
guid: string
atlas: number
rect: {
x: number
y: number
w: number
h: number
}
rotate: number
}[]
_atlas: {
index: number
texture: FileIDPathID
alpha: FileIDPathID
size: number
}
_index: number
_sign: {
m_sprites: SignedItem[]
m_atlases: SignedItem[]
m_alphas: SignedItem[]
}
}
export type AssetsJson = {
filename: string
path?: string
content?: string | Buffer<ArrayBufferLike> | Buffer
}[]

View File

@@ -1,14 +1,11 @@
{ {
"name": "@aklive2d/postcss-config", "name": "@aklive2d/postcss-config",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"postcss": "^8.5.1" "postcss": "^8.5.1"
}, }
"publishConfig": {
"access": "public"
}
} }

View File

@@ -1,14 +1,9 @@
// prettier.config.js, .prettierrc.js, prettier.config.mjs, or .prettierrc.mjs // prettier.config.js, .prettierrc.js, prettier.config.mjs, or .prettierrc.mjs
/**
* @see https://prettier.io/docs/en/configuration.html
* @type {import("prettier").Config}
*/
const config = { const config = {
trailingComma: "es5", trailingComma: 'es5',
tabWidth: 4, tabWidth: 4,
semi: false, semi: false,
singleQuote: true, singleQuote: true,
}; }
export default config; export default config

View File

@@ -1,11 +1,12 @@
{ {
"name": "@aklive2d/prettier-config", "name": "@aklive2d/prettier-config",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"license": "MIT", "license": "MIT",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
"peerDependencies": { "peerDependencies": {
"prettier": ">=3.0.0" "prettier": ">=3.0.0",
} "typescript": ">=5.8.2"
}
} }

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} */ /** @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

@@ -1,15 +1,22 @@
import path from 'node:path' import path from 'node:path'
import Matcher from './libs/content_processor.js' import Matcher from './libs/content_processor.ts'
import { yaml, file } from '@aklive2d/libs' import { yaml, file } from '@aklive2d/libs'
import config from '@aklive2d/config' import config from '@aklive2d/config'
import operators, { OPERATOR_SOURCE_FOLDER } from '@aklive2d/operator' import operators, { OPERATOR_SOURCE_FOLDER } from '@aklive2d/operator'
import { files as backgrounds } from '@aklive2d/background' import { files as backgrounds } from '@aklive2d/background'
import { mapping as musics } from '@aklive2d/music' import { mapping as musics } from '@aklive2d/music'
import { getLangs } from '@aklive2d/charword-table' import { getLangs } from '@aklive2d/charword-table'
import type { ScalarTag } from 'yaml'
import type {
Assets,
TemplateYAML,
ProjectJSON,
ProjectJSONProperty,
} from './types.ts'
const DIST_DIR = path.join(import.meta.dirname, config.dir_name.dist) const DIST_DIR = path.join(import.meta.dirname, config.dir_name.dist)
export const build = (namesToBuild) => { export const build = (namesToBuild: string[]) => {
const err = [] const err = []
const names = !namesToBuild.length ? Object.keys(operators) : namesToBuild const names = !namesToBuild.length ? Object.keys(operators) : namesToBuild
console.log('Generating assets for', names.length, 'operators') console.log('Generating assets for', names.length, 'operators')
@@ -43,15 +50,15 @@ export const build = (namesToBuild) => {
return err return err
} }
const getDistDir = (name) => { const getDistDir = (name: string) => {
return path.join(DIST_DIR, name) return path.join(DIST_DIR, name)
} }
const getDefaultPath = (name) => { const getDefaultPath = (name: string) => {
return path.join(OPERATOR_SOURCE_FOLDER, name) return path.join(OPERATOR_SOURCE_FOLDER, name)
} }
const getPath = (name) => { const getPath = (name: string) => {
// if exists, do not use the template // if exists, do not use the template
const defaultPath = path.join( const defaultPath = path.join(
getDefaultPath(name), getDefaultPath(name),
@@ -67,7 +74,15 @@ const getPath = (name) => {
} }
} }
const process = ({ name, data, template }) => { const process = ({
name,
data,
template,
}: {
name: string
data: ProjectJSON
template: TemplateYAML
}) => {
return { return {
...data, ...data,
description: template.description, description: template.description,
@@ -79,12 +94,14 @@ const process = ({ name, data, template }) => {
...getProperties(template), ...getProperties(template),
}, },
}, },
} } as ProjectJSON
} }
const getProperties = (template) => { const getProperties = (template: TemplateYAML) => {
const properties = template.properties const properties = template.properties
const output = {} const output = {} as {
[key: string]: ProjectJSONProperty
}
for (let i = 0; i < properties.length; i++) { for (let i = 0; i < properties.length; i++) {
output[properties[i].key] = { output[properties[i].key] = {
index: i, index: i,
@@ -95,13 +112,15 @@ const getProperties = (template) => {
return output return output
} }
const load = async (name, assets) => { const load = async (name: string, assets: Assets) => {
// load json from file // load json from file
let data = JSON.parse(await file.read(getPath(name))) let data = JSON.parse(await file.read(getPath(name))) as ProjectJSON
const matcher = new Matcher('~{', '}', operators[name], { const matcher = new Matcher('~{', '}', operators[name], {
...assets, ...assets,
...(() => { ...(() => {
const output = {} const output = {} as {
[key: string]: { label: string; value: string }[]
}
for (const [key, value] of Object.entries(assets)) { for (const [key, value] of Object.entries(assets)) {
output[`${key}Options`] = value.map((b) => { output[`${key}Options`] = value.map((b) => {
return { return {
@@ -114,20 +133,21 @@ const load = async (name, assets) => {
})(), })(),
}) })
const match = { const match = {
identify: (value) => value.startsWith('!match'), identify: (value: unknown) =>
typeof value === 'string' && value.startsWith('!match'),
tag: '!match', tag: '!match',
resolve(str) { resolve(str: string) {
matcher.content = str matcher.content = str
return matcher.result return matcher.result
}, },
} } as ScalarTag
const template = yaml.read( const template = yaml.read(
path.resolve( path.resolve(
import.meta.dirname, import.meta.dirname,
config.module.project_json.template_yaml config.module.project_json.template_yaml
), ),
[match] [match]
) ) as TemplateYAML
data = process({ data = process({
name, name,
data, data,

View File

@@ -1,11 +1,20 @@
import type { OperatorConfig, Codename } from '@aklive2d/operator/types'
import type { Assets } from '../types.ts'
export default class Matcher { export default class Matcher {
#start #start
#end #end
#reExp #reExp
#config #config
#assets #assets
content: string = ''
constructor(start, end, config, assets) { constructor(
start: string,
end: string,
config: OperatorConfig,
assets: Assets
) {
this.#start = start this.#start = start
this.#end = end this.#end = end
this.#reExp = new RegExp(`${start}.+?${end}`, 'g') this.#reExp = new RegExp(`${start}.+?${end}`, 'g')
@@ -40,27 +49,50 @@ class Evalable {
#config #config
#assets #assets
constructor(config, assets) { constructor(config: OperatorConfig, assets: Assets) {
this.#config = config this.#config = config
this.#assets = assets this.#assets = assets
} }
split(location, varName, separator) { split(location: 'config' | 'assets', varName: string, separator: string) {
return this.#step(location, varName).split(separator) return (this.#step(location, varName) as string).split(separator)
} }
var(location, varName) { var(location: 'config' | 'assets', varName: string) {
return this.#step(location, varName) return this.#step(location, varName)
} }
#step(location, varName) { #step(
let content = this.#config location: 'config' | 'assets',
varName: string
):
| OperatorConfig
| Assets
| string
| number
| boolean
| Codename
| string[] {
let content:
| OperatorConfig
| Assets
| string
| number
| boolean
| Codename
| string[] = this.#config
if (location === 'assets') content = this.#assets if (location === 'assets') content = this.#assets
varName.split('->').forEach((item) => { varName.split('->').forEach((item) => {
try { try {
content = content[item] if (location === 'config') {
} catch (e) { content = (content as OperatorConfig)[
throw new Error(`Cannot step ${varName}.`, e) item as keyof OperatorConfig
]
} else {
content = (content as Assets)[item as keyof Assets]
}
} catch (e: unknown) {
throw new Error(`Cannot step ${varName}. ${e}`)
} }
}) })
return content return content

View File

@@ -1,7 +1,7 @@
{ {
"name": "@aklive2d/project-json", "name": "@aklive2d/project-json",
"version": "0.0.0", "version": "0.0.0",
"main": "index.js", "main": "index.ts",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -14,8 +14,13 @@
"@aklive2d/music": "workspace:*", "@aklive2d/music": "workspace:*",
"@aklive2d/prettier-config": "workspace:*" "@aklive2d/prettier-config": "workspace:*"
}, },
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"scripts": { "scripts": {
"build": "mode=build node runner.js", "build": "mode=build bun runner.ts",
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check ." "lint": "eslint && prettier --check ."
} }
} }

View File

@@ -14,7 +14,7 @@ async function main() {
multiple: true, multiple: true,
default: [], default: [],
}, },
}) }) as { mode: string; name: string[] }
switch (mode) { switch (mode) {
case 'build': case 'build':
err = build(name) err = build(name)

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,66 @@
export type Assets = {
backgrounds: string[]
voiceLangs: string[]
subtitleLangs: string[]
music: string[]
}
export interface Property {
text: string
type?: 'bool' | 'file' | 'slider' | 'combo' | 'textinput'
value?: boolean | string
condition?: string
fraction?: boolean
max?: number
min?: number
step?: number
precision?: number
options?: string[]
fileType?: 'video'
}
export interface ProjectJSONProperty extends Property {
index: number
order: number
}
interface Template {
description: string
localization: {
'en-us': Record<string, string>
'zh-chs': Record<string, string>
}
}
export interface TemplateYAML extends Template {
properties: {
key: string
value: Property
}[]
}
interface TemplateJSON extends Template {
properties: {
[key: string]: ProjectJSONProperty
}
}
export interface ProjectJSONBase {
contentrating: string
description: string
file: string
preview: string
ratingsex: string
ratingviolence: string
snapshotformat: number
snapshotoverlay: string
tags: string[]
title: string
type: string
version: number
workshopid: string
}
export interface ProjectJSON extends ProjectJSONBase {
general: TemplateJSON
}

View File

@@ -1,15 +1,12 @@
{ {
"name": "@aklive2d/stylelint-config", "name": "@aklive2d/stylelint-config",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"license": "MIT", "license": "MIT",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"stylelint-config-standard-scss": "^14.0.0", "stylelint-config-standard-scss": "^14.0.0",
"stylelint-prettier": "^5.0.3" "stylelint-prettier": "^5.0.3"
}, }
"publishConfig": {
"access": "public"
}
} }

Some files were not shown because too many files have changed in this diff Show More