feat: migrate to turbo (#22)
* feat: migrate top turbo * ci: ci test * fix: fix codeql issues * feat: ci test * chore: lint * chore: misc changes * feat: rename vite helpers * feat: use fetch to handle assets * feat: update directory * feat: fetch charword table * feat: migrate download game data and detect missing voice files * feat: symlink relative path * feat: finish wrangler upload * feat: migrate wrangler download * feat: finish * chore: auto update * ci: update ci * ci: update ci --------- Co-authored-by: Halyul <Halyul@users.noreply.github.com>
This commit is contained in:
1
packages/assets/.gitignore
vendored
Normal file
1
packages/assets/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
data
|
||||
3
packages/assets/.prettierignore
Normal file
3
packages/assets/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
dist
|
||||
data
|
||||
auto_update
|
||||
8
packages/assets/config.yaml
Normal file
8
packages/assets/config.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
dynchars: dynchars
|
||||
item_to_download:
|
||||
- homebackground/wrapper
|
||||
- ui_camp_logo
|
||||
- charportraits
|
||||
- voice.*/extra
|
||||
additional_regex:
|
||||
- ^(?!(avg))(.*)$
|
||||
3
packages/assets/eslint.config.js
Normal file
3
packages/assets/eslint.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import baseConfig from '@aklive2d/eslint-config'
|
||||
/** @type {import('eslint').Config} */
|
||||
export default [...baseConfig]
|
||||
12
packages/assets/index.js
Normal file
12
packages/assets/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import path from 'node:path'
|
||||
import config from '@aklive2d/config'
|
||||
import { yaml } from '@aklive2d/libs'
|
||||
|
||||
export const DIST_DIR = path.resolve(import.meta.dirname, config.dir_name.dist)
|
||||
export const CONFIG_PATH = path.resolve(
|
||||
import.meta.dirname,
|
||||
config.dir_name.config_yaml
|
||||
)
|
||||
const selfConfig = yaml.read(CONFIG_PATH)
|
||||
|
||||
export default selfConfig
|
||||
49
packages/assets/libs/build.js
Normal file
49
packages/assets/libs/build.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import path from 'node:path'
|
||||
import { file } from '@aklive2d/libs'
|
||||
import config from '@aklive2d/config'
|
||||
import { DIST_DIR } from '../index.js'
|
||||
|
||||
export default async (packageDir) => {
|
||||
const copyQueue = [
|
||||
{
|
||||
fn: file.symlink,
|
||||
source: path.resolve(
|
||||
packageDir,
|
||||
'background',
|
||||
config.dir_name.dist
|
||||
),
|
||||
target: path.resolve(DIST_DIR, config.dir_name.background),
|
||||
},
|
||||
{
|
||||
fn: file.symlink,
|
||||
source: path.resolve(
|
||||
packageDir,
|
||||
'charword-table',
|
||||
config.dir_name.dist
|
||||
),
|
||||
target: path.resolve(DIST_DIR, config.dir_name.charword_table),
|
||||
},
|
||||
{
|
||||
fn: file.symlink,
|
||||
source: path.resolve(packageDir, 'music', config.dir_name.data),
|
||||
target: path.resolve(DIST_DIR, config.dir_name.music),
|
||||
},
|
||||
{
|
||||
fn: file.symlinkAll,
|
||||
source: path.resolve(packageDir, 'operator', config.dir_name.dist),
|
||||
target: path.resolve(DIST_DIR),
|
||||
},
|
||||
{
|
||||
fn: file.symlink,
|
||||
source: path.resolve(
|
||||
packageDir,
|
||||
'project-json',
|
||||
config.dir_name.dist
|
||||
),
|
||||
target: path.resolve(DIST_DIR, config.dir_name.project_json),
|
||||
},
|
||||
]
|
||||
copyQueue.map(({ fn, source, target }) => {
|
||||
fn(source, target)
|
||||
})
|
||||
}
|
||||
57
packages/assets/libs/download.js
Normal file
57
packages/assets/libs/download.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import path from 'node:path'
|
||||
import { file } from '@aklive2d/libs'
|
||||
import { unzipDownload } from '@aklive2d/downloader'
|
||||
import { getOperatorId, getOperatorAlternativeId } from '@aklive2d/operator'
|
||||
import { mapping } from '@aklive2d/music'
|
||||
import config from '../index.js'
|
||||
|
||||
export default async (dataDir) => {
|
||||
const pidSet = new Set()
|
||||
const versionRes = await fetch(
|
||||
'https://ak-conf.hypergryph.com/config/prod/official/Android/version'
|
||||
)
|
||||
const version = (await versionRes.json()).resVersion
|
||||
const lpacksRes = await fetch(
|
||||
`https://ak.hycdn.cn/assetbundle/official/Android/assets/${version}/hot_update_list.json`
|
||||
)
|
||||
const updateList = await lpacksRes.json()
|
||||
const itemToDownload = new Set(config.item_to_download)
|
||||
updateList.abInfos.map((item) => {
|
||||
if (item.name.includes(config.dynchars)) {
|
||||
const id = getOperatorId(item.name).replace('.ab', '')
|
||||
itemToDownload.add(id)
|
||||
itemToDownload.add(getOperatorAlternativeId(id))
|
||||
}
|
||||
})
|
||||
mapping.musicFiles.map((item) => {
|
||||
if (!file.exists(path.join(item.source, item.filename))) {
|
||||
const filename = item.filename.replace('.ogg', '')
|
||||
itemToDownload.add(filename)
|
||||
}
|
||||
})
|
||||
const itemToDownloadRegExp = new RegExp(
|
||||
`(.*)(${Array.from(itemToDownload).join('|')})(.*)`
|
||||
)
|
||||
updateList.abInfos.map((item) => {
|
||||
if (itemToDownloadRegExp.test(item.name)) {
|
||||
item.pid && pidSet.add(item.pid)
|
||||
}
|
||||
})
|
||||
const lpacksToDownload = []
|
||||
pidSet.forEach((item) => {
|
||||
lpacksToDownload.push({
|
||||
name: item,
|
||||
url: `https://ak.hycdn.cn/assetbundle/official/Android/assets/${version}/${item}.dat`,
|
||||
})
|
||||
})
|
||||
const regexs = []
|
||||
if (config.additional_regex.length > 0) {
|
||||
for (const item of config.additional_regex) {
|
||||
regexs.push(new RegExp(item))
|
||||
}
|
||||
}
|
||||
await unzipDownload(lpacksToDownload, dataDir, {
|
||||
matchRegExps: regexs,
|
||||
defaultRegex: itemToDownloadRegExp,
|
||||
})
|
||||
}
|
||||
21
packages/assets/package.json
Normal file
21
packages/assets/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@aklive2d/assets",
|
||||
"version": "0.0.0",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@aklive2d/config": "workspace:*",
|
||||
"@aklive2d/eslint-config": "workspace:*",
|
||||
"@aklive2d/libs": "workspace:*",
|
||||
"@aklive2d/prettier-config": "workspace:*",
|
||||
"@aklive2d/downloader": "workspace:*",
|
||||
"@aklive2d/operator": "workspace:*",
|
||||
"@aklive2d/music": "workspace:*"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "mode=build node runner.js",
|
||||
"download:game": "mode=download node runner.js",
|
||||
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check ."
|
||||
}
|
||||
}
|
||||
11
packages/assets/prettier.config.js
Normal file
11
packages/assets/prettier.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import baseConfig from '@aklive2d/prettier-config'
|
||||
|
||||
/**
|
||||
* @type {import("prettier").Config}
|
||||
*/
|
||||
const config = {
|
||||
...baseConfig,
|
||||
semi: false,
|
||||
}
|
||||
|
||||
export default config
|
||||
30
packages/assets/runner.js
Normal file
30
packages/assets/runner.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
import { envParser } from '@aklive2d/libs'
|
||||
import config from '@aklive2d/config'
|
||||
import build from './libs/build.js'
|
||||
import download from './libs/download.js'
|
||||
|
||||
const packageDir = path.resolve(import.meta.dirname, '..')
|
||||
const dataDir = path.resolve(import.meta.dirname, config.dir_name.data)
|
||||
|
||||
async function main() {
|
||||
const { mode } = envParser.parse({
|
||||
mode: {
|
||||
type: 'string',
|
||||
short: 'm',
|
||||
},
|
||||
})
|
||||
switch (mode) {
|
||||
case 'build':
|
||||
await build(packageDir)
|
||||
break
|
||||
case 'download':
|
||||
await download(dataDir)
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unknown mode: ${mode}`)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
1
packages/background/.gitignore
vendored
Normal file
1
packages/background/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
data
|
||||
3
packages/background/.prettierignore
Normal file
3
packages/background/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
dist
|
||||
data
|
||||
auto_update
|
||||
3
packages/background/eslint.config.js
Normal file
3
packages/background/eslint.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import baseConfig from '@aklive2d/eslint-config'
|
||||
/** @type {import('eslint').Config} */
|
||||
export default [...baseConfig]
|
||||
84
packages/background/index.js
Normal file
84
packages/background/index.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import path from 'node:path'
|
||||
import sharp from 'sharp'
|
||||
import { file } from '@aklive2d/libs'
|
||||
import config from '@aklive2d/config'
|
||||
import { mapping as musicMapping } from '@aklive2d/music'
|
||||
|
||||
export const BACKGROUND_DIR = path.join(
|
||||
import.meta.dirname,
|
||||
config.dir_name.data
|
||||
)
|
||||
const DIST_DIR = path.resolve(import.meta.dirname, config.dir_name.dist)
|
||||
const EXTRACTED_DIR = path.join(BACKGROUND_DIR, config.dir_name.extracted)
|
||||
const DEFAULT_BACKGROUND_FILE = path.join(
|
||||
BACKGROUND_DIR,
|
||||
config.module.background.operator_bg_png
|
||||
)
|
||||
|
||||
const getFiles = () => {
|
||||
return file.readdirSync(DIST_DIR)
|
||||
}
|
||||
|
||||
export let files = getFiles()
|
||||
|
||||
const filesToBuild = file.readdirSync(EXTRACTED_DIR).filter((f) => {
|
||||
return f.endsWith('.png') && f.includes('_left')
|
||||
})
|
||||
|
||||
export const build = async () => {
|
||||
const err = []
|
||||
file.mkdir(DIST_DIR)
|
||||
await Promise.all(
|
||||
filesToBuild.map(async (f) => {
|
||||
const filenamePrefix = path.parse(f).name.replace('_left', '')
|
||||
await composite(filenamePrefix, '.png')
|
||||
})
|
||||
)
|
||||
await file.copy(
|
||||
DEFAULT_BACKGROUND_FILE,
|
||||
path.join(DIST_DIR, config.module.background.operator_bg_png)
|
||||
)
|
||||
|
||||
const { musicFiles, musicFileMapping } = musicMapping
|
||||
|
||||
for (const e of musicFiles) {
|
||||
const musicPath = path.join(e.source, e.filename)
|
||||
if (!file.exists(musicPath)) {
|
||||
err.push(`Music file ${e.filename} is not found in music folder.`)
|
||||
}
|
||||
}
|
||||
files = getFiles()
|
||||
for (const e of Object.keys(musicFileMapping)) {
|
||||
if (!files.includes(e)) {
|
||||
err.push(`Background file ${e} is not found in background folder.`)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
const composite = async (filenamePrefix, fileExt) => {
|
||||
const image = sharp(
|
||||
path.join(EXTRACTED_DIR, `${filenamePrefix}_left${fileExt}`)
|
||||
)
|
||||
const metadata = await image.metadata()
|
||||
|
||||
image
|
||||
.resize(2 * metadata.width, metadata.height, {
|
||||
kernel: sharp.kernel.nearest,
|
||||
fit: 'contain',
|
||||
position: 'left top',
|
||||
background: { r: 255, g: 255, b: 255, alpha: 0 },
|
||||
})
|
||||
.composite([
|
||||
{
|
||||
input: path.join(
|
||||
EXTRACTED_DIR,
|
||||
`${filenamePrefix}_right${fileExt}`
|
||||
),
|
||||
top: 0,
|
||||
left: metadata.width,
|
||||
},
|
||||
])
|
||||
.toFile(path.join(DIST_DIR, `${filenamePrefix}${fileExt}`))
|
||||
}
|
||||
19
packages/background/package.json
Normal file
19
packages/background/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@aklive2d/background",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"sharp": "^0.33.5",
|
||||
"@aklive2d/libs": "workspace:*",
|
||||
"@aklive2d/config": "workspace:*",
|
||||
"@aklive2d/music": "workspace:*",
|
||||
"@aklive2d/eslint-config": "workspace:*",
|
||||
"@aklive2d/prettier-config": "workspace:*"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "mode=build node runner.js",
|
||||
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check ."
|
||||
}
|
||||
}
|
||||
11
packages/background/prettier.config.js
Normal file
11
packages/background/prettier.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import baseConfig from '@aklive2d/prettier-config'
|
||||
|
||||
/**
|
||||
* @type {import("prettier").Config}
|
||||
*/
|
||||
const config = {
|
||||
...baseConfig,
|
||||
semi: false,
|
||||
}
|
||||
|
||||
export default config
|
||||
22
packages/background/runner.js
Normal file
22
packages/background/runner.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { build } from './index.js'
|
||||
import { envParser, error } from '@aklive2d/libs'
|
||||
|
||||
async function main() {
|
||||
let err
|
||||
const { mode } = envParser.parse({
|
||||
mode: {
|
||||
type: 'string',
|
||||
short: 'm',
|
||||
},
|
||||
})
|
||||
switch (mode) {
|
||||
case 'build':
|
||||
err = await build()
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unknown mode: ${mode}`)
|
||||
}
|
||||
error.handle(err)
|
||||
}
|
||||
|
||||
main()
|
||||
3
packages/charword-table/.prettierignore
Normal file
3
packages/charword-table/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
dist
|
||||
data
|
||||
auto_update
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3
packages/charword-table/eslint.config.js
Normal file
3
packages/charword-table/eslint.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import baseConfig from '@aklive2d/eslint-config'
|
||||
/** @type {import('eslint').Config} */
|
||||
export default [...baseConfig]
|
||||
267
packages/charword-table/index.js
Normal file
267
packages/charword-table/index.js
Normal file
@@ -0,0 +1,267 @@
|
||||
import path from 'node:path'
|
||||
import { file } from '@aklive2d/libs'
|
||||
import { githubDownload } from '@aklive2d/downloader'
|
||||
import config from '@aklive2d/config'
|
||||
import operators, {
|
||||
getOperatorId,
|
||||
getOperatorAlternativeId,
|
||||
OPERATOR_SOURCE_FOLDER,
|
||||
} from '@aklive2d/operator'
|
||||
|
||||
// zh_TW uses an older version of charword_table.json
|
||||
// zh_TW is removed
|
||||
const REGIONS = ['zh_CN', 'en_US', 'ja_JP', 'ko_KR']
|
||||
const REGION_URLS = {
|
||||
zh_CN: 'Kengxxiao/ArknightsGameData',
|
||||
en_US: 'Kengxxiao/ArknightsGameData_YoStar',
|
||||
ja_JP: 'Kengxxiao/ArknightsGameData_YoStar',
|
||||
ko_KR: 'Kengxxiao/ArknightsGameData_YoStar',
|
||||
}
|
||||
const DEFAULT_REGION = REGIONS[0]
|
||||
export const defaultRegion = DEFAULT_REGION.replace('_', '-')
|
||||
const NICKNAME = {
|
||||
zh_CN: '博士',
|
||||
en_US: 'Doctor',
|
||||
ja_JP: 'ドクター',
|
||||
ko_KR: '박사',
|
||||
zh_TW: '博士',
|
||||
}
|
||||
|
||||
const OPERATOR_IDS = Object.values(operators).map((operator) => {
|
||||
return getOperatorId(operator.filename)
|
||||
})
|
||||
const AUTO_UPDATE_FOLDER = path.resolve(
|
||||
import.meta.dirname,
|
||||
config.dir_name.auto_update
|
||||
)
|
||||
const CHARWORD_TABLE_FILE = path.resolve(
|
||||
AUTO_UPDATE_FOLDER,
|
||||
config.module.charword_table.charword_table_json
|
||||
)
|
||||
const CHARWORD_TABLE = JSON.parse(file.readSync(CHARWORD_TABLE_FILE)) || {}
|
||||
const DIST_DIR = path.resolve(import.meta.dirname, config.dir_name.dist)
|
||||
|
||||
export const lookup = (operatorName) => {
|
||||
const operatorId = getOperatorId(operators[operatorName].filename)
|
||||
const operatorBlock = CHARWORD_TABLE[operatorId]
|
||||
return operatorBlock.ref
|
||||
? CHARWORD_TABLE[operatorBlock.alternativeId]
|
||||
: operatorBlock
|
||||
}
|
||||
|
||||
const getDistDir = (name) => {
|
||||
return path.join(
|
||||
DIST_DIR,
|
||||
name,
|
||||
config.module.charword_table.charword_table_json
|
||||
)
|
||||
}
|
||||
|
||||
export const getLangs = (name, voiceJson = null) => {
|
||||
voiceJson = voiceJson
|
||||
? voiceJson
|
||||
: JSON.parse(file.readSync(getDistDir(name)))
|
||||
const voiceLangs = Object.keys(voiceJson.voiceLangs['zh-CN'])
|
||||
const subtitleLangs = Object.keys(voiceJson.subtitleLangs)
|
||||
return { voiceLangs, subtitleLangs }
|
||||
}
|
||||
|
||||
export const build = async (namesToBuild) => {
|
||||
const err = []
|
||||
const names = !namesToBuild.length ? Object.keys(operators) : namesToBuild
|
||||
console.log('Generating charword_table for', names.length, 'operators')
|
||||
await updateFn(true)
|
||||
for (const name of names) {
|
||||
const charwordTableLookup = lookup(name)
|
||||
const voiceJson = {}
|
||||
voiceJson.voiceLangs = {}
|
||||
voiceJson.subtitleLangs = {}
|
||||
const subtitleInfo = Object.keys(charwordTableLookup.info)
|
||||
let voiceList = {}
|
||||
subtitleInfo.forEach((item) => {
|
||||
if (Object.keys(charwordTableLookup.info[item]).length > 0) {
|
||||
const key = item.replace('_', '-')
|
||||
voiceJson.subtitleLangs[key] = {}
|
||||
for (const [id, subtitles] of Object.entries(
|
||||
charwordTableLookup.voice[item]
|
||||
)) {
|
||||
const match = id.replace(/(.+?)([A-Z]\w+)/, '$2')
|
||||
if (match === id) {
|
||||
voiceJson.subtitleLangs[key].default = subtitles
|
||||
voiceList[key] = Object.keys(subtitles)
|
||||
} else {
|
||||
voiceJson.subtitleLangs[key][match] = subtitles
|
||||
}
|
||||
}
|
||||
voiceJson.voiceLangs[key] = {}
|
||||
Object.values(charwordTableLookup.info[item]).forEach(
|
||||
(item) => {
|
||||
voiceJson.voiceLangs[key] = {
|
||||
...voiceJson.voiceLangs[key],
|
||||
...item,
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
let voiceLangs = []
|
||||
try {
|
||||
voiceLangs = getLangs(name, voiceJson).voiceLangs
|
||||
|
||||
file.writeSync(JSON.stringify(voiceJson), getDistDir(name))
|
||||
} catch (e) {
|
||||
console.log(`charword_table is not available`, e)
|
||||
}
|
||||
|
||||
// check whether voice files has been added
|
||||
const customVoiceName = voiceLangs.filter(
|
||||
(i) => !config.dir_name.voice.sub.map((e) => e.lang).includes(i)
|
||||
)[0]
|
||||
const voiceLangMapping = config.dir_name.voice.sub
|
||||
.filter((e) => {
|
||||
return (
|
||||
voiceLangs.includes(e.lang) ||
|
||||
(e.lang === 'CUSTOM' &&
|
||||
typeof customVoiceName !== 'undefined')
|
||||
)
|
||||
})
|
||||
.map((e) => {
|
||||
return {
|
||||
name: e.name,
|
||||
lang: e.lang === 'CUSTOM' ? customVoiceName : e.lang,
|
||||
lookup_region: e.lookup_region.replace('_', '-'),
|
||||
}
|
||||
})
|
||||
for (const voiceSubFolderMapping of voiceLangMapping) {
|
||||
const voiceSubFolder = path.join(
|
||||
OPERATOR_SOURCE_FOLDER,
|
||||
name,
|
||||
config.dir_name.voice.main,
|
||||
voiceSubFolderMapping.name
|
||||
)
|
||||
const voiceFileList = file.readdirSync(voiceSubFolder)
|
||||
voiceList[voiceSubFolderMapping.lookup_region].map((item) => {
|
||||
if (!voiceFileList.includes(`${item}.ogg`))
|
||||
err.push(
|
||||
`Voice folder ${voiceSubFolderMapping.name} for ${name} is missing ${item}.ogg`
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
export const update = async () => {
|
||||
await updateFn()
|
||||
}
|
||||
|
||||
const updateFn = async (isLocalOnly = false) => {
|
||||
const regionObject = REGIONS.reduce(
|
||||
(acc, cur) => ({ ...acc, [cur]: {} }),
|
||||
{}
|
||||
)
|
||||
OPERATOR_IDS.forEach((id) => {
|
||||
CHARWORD_TABLE[id] = {
|
||||
alternativeId: getOperatorAlternativeId(id),
|
||||
voice: structuredClone(regionObject),
|
||||
info: structuredClone(regionObject),
|
||||
}
|
||||
})
|
||||
await load(DEFAULT_REGION, isLocalOnly)
|
||||
await Promise.all(
|
||||
REGIONS.slice(1).map(async (region) => {
|
||||
await load(region, isLocalOnly)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const load = async (region, isLocalOnly = false) => {
|
||||
const basename = `charword_table_${region}`
|
||||
const filename = file
|
||||
.readdirSync(AUTO_UPDATE_FOLDER)
|
||||
.filter((item) => item.startsWith(`charword_table_${region}`))[0]
|
||||
const localFilePath = path.join(AUTO_UPDATE_FOLDER, filename)
|
||||
const data = isLocalOnly
|
||||
? JSON.parse(file.readSync(localFilePath))
|
||||
: await download(
|
||||
region,
|
||||
path.join(path.dirname(localFilePath), `${basename}.json`)
|
||||
)
|
||||
|
||||
// put voice actor info into charword_table
|
||||
for (const [id, element] of Object.entries(CHARWORD_TABLE)) {
|
||||
let operatorId = id
|
||||
let useAlternativeId = false
|
||||
if (typeof data.voiceLangDict[operatorId] === 'undefined') {
|
||||
operatorId = element.alternativeId
|
||||
useAlternativeId = true
|
||||
}
|
||||
if (region === DEFAULT_REGION) {
|
||||
element.infile = OPERATOR_IDS.includes(operatorId)
|
||||
element.ref = useAlternativeId && element.infile
|
||||
}
|
||||
// not available in other region
|
||||
if (typeof data.voiceLangDict[operatorId] === 'undefined') {
|
||||
console.log(
|
||||
`Voice actor info of ${id} is not available in ${region}.`
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
if (element.infile && useAlternativeId) {
|
||||
// if using alternative id and infile is true, means data can be
|
||||
// refered inside the file
|
||||
// if infile is false, useAlternativeId is always true
|
||||
// if useAlternativeId is false, infile is always true
|
||||
// | case | infile | useAlternativeId | Note |
|
||||
// | ------------------- | ------ | ---------------- | --------------- |
|
||||
// | lee_trust_your_eyes | false | true | skin only |
|
||||
// | nearl_relight | true | true | skin, operator, no voice |
|
||||
// | nearl | true | false | operator only |
|
||||
// | w_fugue | true | false | skin, operator, voice |
|
||||
continue
|
||||
}
|
||||
Object.values(data.voiceLangDict[operatorId].dict).forEach((item) => {
|
||||
if (typeof element.info[region][item.wordkey] === 'undefined') {
|
||||
element.info[region][item.wordkey] = {}
|
||||
}
|
||||
element.info[region][item.wordkey][item.voiceLangType] = [
|
||||
...(typeof item.cvName === 'string'
|
||||
? [item.cvName]
|
||||
: item.cvName),
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// put voice lines into charword_table
|
||||
Object.values(data.charWords).forEach((item) => {
|
||||
const operatorInfo = Object.values(CHARWORD_TABLE).filter(
|
||||
(element) => element.info[region][item.wordKey]
|
||||
)
|
||||
if (operatorInfo.length > 0) {
|
||||
for (const operator of operatorInfo) {
|
||||
if (
|
||||
typeof operator.voice[region][item.wordKey] === 'undefined'
|
||||
) {
|
||||
operator.voice[region][item.wordKey] = {}
|
||||
}
|
||||
operator.voice[region][item.wordKey][item.voiceId] = {
|
||||
title: item.voiceTitle,
|
||||
text: item.voiceText.replace(
|
||||
/{@nickname}/g,
|
||||
NICKNAME[region]
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const download = async (region, targetFilePath) => {
|
||||
return await githubDownload(
|
||||
`https://api.github.com/repos/${REGION_URLS[region]}/commits?path=${region}/gamedata/excel/charword_table.json`,
|
||||
`https://raw.githubusercontent.com/${REGION_URLS[region]}/master/${region}/gamedata/excel/charword_table.json`,
|
||||
targetFilePath
|
||||
)
|
||||
}
|
||||
20
packages/charword-table/package.json
Normal file
20
packages/charword-table/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@aklive2d/charword-table",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@aklive2d/libs": "workspace:*",
|
||||
"@aklive2d/config": "workspace:*",
|
||||
"@aklive2d/downloader": "workspace:*",
|
||||
"@aklive2d/operator": "workspace:*",
|
||||
"@aklive2d/eslint-config": "workspace:*",
|
||||
"@aklive2d/prettier-config": "workspace:*"
|
||||
},
|
||||
"scripts": {
|
||||
"update": "mode=update node runner.js",
|
||||
"build": "mode=build node runner.js",
|
||||
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check ."
|
||||
}
|
||||
}
|
||||
11
packages/charword-table/prettier.config.js
Normal file
11
packages/charword-table/prettier.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import baseConfig from '@aklive2d/prettier-config'
|
||||
|
||||
/**
|
||||
* @type {import("prettier").Config}
|
||||
*/
|
||||
const config = {
|
||||
...baseConfig,
|
||||
semi: false,
|
||||
}
|
||||
|
||||
export default config
|
||||
31
packages/charword-table/runner.js
Normal file
31
packages/charword-table/runner.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { build, update } from './index.js'
|
||||
import { envParser, error } from '@aklive2d/libs'
|
||||
|
||||
async function main() {
|
||||
let err = []
|
||||
const { mode, name } = envParser.parse({
|
||||
mode: {
|
||||
type: 'string',
|
||||
short: 'm',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
short: 'n',
|
||||
multiple: true,
|
||||
default: [],
|
||||
},
|
||||
})
|
||||
switch (mode) {
|
||||
case 'build':
|
||||
err = await build(name)
|
||||
break
|
||||
case 'update':
|
||||
await update()
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unknown mode: ${mode}`)
|
||||
}
|
||||
error.handle(err)
|
||||
}
|
||||
|
||||
main()
|
||||
3
packages/config/.prettierignore
Normal file
3
packages/config/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
dist
|
||||
data
|
||||
auto_update
|
||||
96
packages/config/config.yaml
Normal file
96
packages/config/config.yaml
Normal file
@@ -0,0 +1,96 @@
|
||||
site_id: aklive2d
|
||||
akassets:
|
||||
project_name: akassets
|
||||
url: https://migrate-turbo.akassets.pages.dev
|
||||
insight:
|
||||
id: aklive2d
|
||||
url: https://insight.halyul.dev/on-demand.js
|
||||
module:
|
||||
background:
|
||||
background: background
|
||||
operator_bg_png: operator_bg.png
|
||||
charword_table:
|
||||
charword_table_json: charword_table.json
|
||||
music:
|
||||
music_table_json: music_table.json
|
||||
display_meta_table_json: display_meta_table.json
|
||||
audio_data_json: audio_data.json
|
||||
official_info:
|
||||
official_info_json: official_info.json
|
||||
operator:
|
||||
config: config
|
||||
template_yaml: _template.yaml
|
||||
config_yaml: config.yaml
|
||||
portraits: _portraits
|
||||
logos_assets: _logos
|
||||
directory_assets: _directory
|
||||
MonoBehaviour: MonoBehaviour
|
||||
Texture2D: Texture2D
|
||||
assets_json: assets.json
|
||||
title:
|
||||
zh-CN: '明日方舟:'
|
||||
en-US: 'Arknights: '
|
||||
project_json:
|
||||
project_json: project.json
|
||||
preview_jpg: preview.jpg
|
||||
template_yaml: project_json.yaml
|
||||
wrangler:
|
||||
index_json: index.json
|
||||
dir_name:
|
||||
config_yaml: config.yaml
|
||||
assets: assets
|
||||
data: data
|
||||
dist: dist
|
||||
extracted: extracted
|
||||
auto_update: auto_update
|
||||
operator: operator
|
||||
background: background
|
||||
music: music
|
||||
logos: logos
|
||||
public: public
|
||||
charword_table: charword_table
|
||||
project_json: project_json
|
||||
config_json: config.json
|
||||
voice:
|
||||
main: voice
|
||||
sub:
|
||||
- name: jp
|
||||
lang: JP
|
||||
lookup_region: zh_CN
|
||||
- name: cn
|
||||
lang: CN_MANDARIN
|
||||
lookup_region: zh_CN
|
||||
- name: en
|
||||
lang: EN
|
||||
lookup_region: en_US
|
||||
- name: kr
|
||||
lang: KR
|
||||
lookup_region: ko_KR
|
||||
- name: custom
|
||||
lang: CUSTOM
|
||||
lookup_region: zh_CN
|
||||
share:
|
||||
title:
|
||||
zh-CN: '明日方舟:'
|
||||
en-US: 'Arknights: '
|
||||
directory:
|
||||
assets_dir: _assets
|
||||
title: AKLive2D
|
||||
voice: jp/CN_037.ogg
|
||||
error:
|
||||
files:
|
||||
- key: build_char_128_plosis_epoque#3
|
||||
paddings:
|
||||
left: -120
|
||||
right: 150
|
||||
top: 10
|
||||
bottom: 0
|
||||
- key: build_char_128_plosis
|
||||
paddings:
|
||||
left: -90
|
||||
right: 100
|
||||
top: 10
|
||||
bottom: 0
|
||||
voice:
|
||||
file: CN_034.ogg
|
||||
target: error.ogg
|
||||
3
packages/config/eslint.config.js
Normal file
3
packages/config/eslint.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import baseConfig from '@aklive2d/eslint-config'
|
||||
/** @type {import('eslint').Config} */
|
||||
export default [...baseConfig]
|
||||
4
packages/config/index.js
Normal file
4
packages/config/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import path from 'node:path'
|
||||
import { yaml } from '@aklive2d/libs'
|
||||
|
||||
export default yaml.read(path.resolve(import.meta.dirname, 'config.yaml'))
|
||||
15
packages/config/package.json
Normal file
15
packages/config/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "@aklive2d/config",
|
||||
"version": "0.0.0",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@aklive2d/libs": "workspace:*",
|
||||
"@aklive2d/eslint-config": "workspace:*",
|
||||
"@aklive2d/prettier-config": "workspace:*"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check ."
|
||||
}
|
||||
}
|
||||
11
packages/config/prettier.config.js
Normal file
11
packages/config/prettier.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import baseConfig from '@aklive2d/prettier-config'
|
||||
|
||||
/**
|
||||
* @type {import("prettier").Config}
|
||||
*/
|
||||
const config = {
|
||||
...baseConfig,
|
||||
semi: false,
|
||||
}
|
||||
|
||||
export default config
|
||||
3
packages/downloader/.prettierignore
Normal file
3
packages/downloader/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
dist
|
||||
data
|
||||
auto_update
|
||||
3
packages/downloader/eslint.config.js
Normal file
3
packages/downloader/eslint.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import baseConfig from '@aklive2d/eslint-config'
|
||||
/** @type {import('eslint').Config} */
|
||||
export default [...baseConfig]
|
||||
112
packages/downloader/index.js
Normal file
112
packages/downloader/index.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
import { Buffer } from 'node:buffer'
|
||||
import { pipeline } from 'node:stream/promises'
|
||||
import pThrottle from 'p-throttle'
|
||||
import { file as fileLib } from '@aklive2d/libs'
|
||||
|
||||
export const githubDownload = async (history_url, raw_url, filepath) => {
|
||||
const historyResponse = await fetch(history_url)
|
||||
const historyData = await historyResponse.json()
|
||||
const lastCommit = historyData[0]
|
||||
const lastCommitDate = new Date(lastCommit.commit.committer.date)
|
||||
const ext = path.extname(filepath)
|
||||
const basename = path.basename(filepath).replace(ext, '')
|
||||
filepath = path.join(
|
||||
path.dirname(filepath),
|
||||
`${basename}_${lastCommitDate.getTime()}${ext}`
|
||||
)
|
||||
const dirpath = path.dirname(filepath)
|
||||
console.log(`Last ${basename} commit date: ${lastCommitDate.getTime()}`)
|
||||
|
||||
if (fileLib.exists(filepath)) {
|
||||
console.log(`${basename} is the latest version.`)
|
||||
return JSON.parse(fileLib.readSync(filepath))
|
||||
}
|
||||
const response = await fetch(raw_url)
|
||||
const data = await response.json()
|
||||
fileLib.writeSync(JSON.stringify(data), filepath)
|
||||
console.log(`${basename} is updated.`)
|
||||
|
||||
// remove old file
|
||||
const files = fileLib.readdirSync(path.join(dirpath))
|
||||
for (const file of files) {
|
||||
if (file.startsWith(basename) && file !== path.basename(filepath)) {
|
||||
fileLib.rm(path.join(dirpath, file))
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
export const unzipDownload = async (
|
||||
filesToDownload,
|
||||
targetDir,
|
||||
opts = {
|
||||
defaultRegex: null,
|
||||
matchRegExps: [],
|
||||
}
|
||||
) => {
|
||||
let retry = filesToDownload
|
||||
const throttle = pThrottle({
|
||||
limit: 3,
|
||||
interval: 1000,
|
||||
})
|
||||
while (retry.length > 0) {
|
||||
const newRetry = []
|
||||
await Promise.all(
|
||||
retry.map(
|
||||
throttle(async (item) => {
|
||||
const name = item.name
|
||||
console.log(`Downloading ${name}`)
|
||||
const zip = await fetch(item.url)
|
||||
.then((resp) => {
|
||||
const status = resp.status
|
||||
if (status !== 200)
|
||||
throw new Error(`Status Code: ${status}`)
|
||||
return resp.arrayBuffer()
|
||||
})
|
||||
.then((arrayBuffer) => {
|
||||
return fileLib.unzip.fromBuffer(
|
||||
Buffer.from(arrayBuffer)
|
||||
)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
try {
|
||||
for await (const entry of zip) {
|
||||
if (
|
||||
opts.defaultRegex &&
|
||||
!opts.defaultRegex.test(entry.filename)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
if (opts.matchRegExps.length > 0) {
|
||||
let shallContinue = false
|
||||
for (const regex of opts.matchRegExps) {
|
||||
if (!regex.test(entry.filename)) {
|
||||
shallContinue = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (shallContinue) continue
|
||||
}
|
||||
const filePath = path.join(
|
||||
targetDir,
|
||||
entry.filename
|
||||
)
|
||||
fileLib.mkdir(path.dirname(filePath))
|
||||
const readStream = await entry.openReadStream()
|
||||
const writeStream = fs.createWriteStream(filePath)
|
||||
await pipeline(readStream, writeStream)
|
||||
console.log(`Finish Writing to ${entry.filename}`)
|
||||
}
|
||||
} finally {
|
||||
await zip.close()
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
retry = newRetry
|
||||
}
|
||||
}
|
||||
16
packages/downloader/package.json
Normal file
16
packages/downloader/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "@aklive2d/downloader",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@aklive2d/eslint-config": "workspace:*",
|
||||
"@aklive2d/libs": "workspace:*",
|
||||
"@aklive2d/prettier-config": "workspace:*",
|
||||
"p-throttle": "^7.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check ."
|
||||
}
|
||||
}
|
||||
11
packages/downloader/prettier.config.js
Normal file
11
packages/downloader/prettier.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import baseConfig from '@aklive2d/prettier-config'
|
||||
|
||||
/**
|
||||
* @type {import("prettier").Config}
|
||||
*/
|
||||
const config = {
|
||||
...baseConfig,
|
||||
semi: false,
|
||||
}
|
||||
|
||||
export default config
|
||||
23
packages/eslint-config/index.js
Normal file
23
packages/eslint-config/index.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import js from '@eslint/js'
|
||||
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
|
||||
import globals from 'globals'
|
||||
|
||||
export default [
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
files: ['**/*.{js,jsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
ecmaFeatures: { jsx: true },
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
...js.configs.recommended.rules,
|
||||
},
|
||||
},
|
||||
eslintPluginPrettierRecommended,
|
||||
]
|
||||
17
packages/eslint-config/package.json
Normal file
17
packages/eslint-config/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@aklive2d/eslint-config",
|
||||
"version": "0.0.0",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint/js": "^9.19.0",
|
||||
"eslint": "^9.19.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-prettier": "^5.2.3",
|
||||
"globals": "^15.14.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
3
packages/libs/.prettierignore
Normal file
3
packages/libs/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
dist
|
||||
data
|
||||
auto_update
|
||||
3
packages/libs/eslint.config.js
Normal file
3
packages/libs/eslint.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import baseConfig from '@aklive2d/eslint-config'
|
||||
/** @type {import('eslint').Config} */
|
||||
export default [...baseConfig]
|
||||
13
packages/libs/index.js
Normal file
13
packages/libs/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
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 }
|
||||
48
packages/libs/libs/alpha_composite.js
Normal file
48
packages/libs/libs/alpha_composite.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import sharp from 'sharp'
|
||||
import path from 'path'
|
||||
|
||||
export const process = async (filename, maskFilename, extractedDir) => {
|
||||
const image = sharp(path.join(extractedDir, filename)).removeAlpha()
|
||||
const imageMeta = await image.metadata()
|
||||
const imageBuffer = await image.toBuffer()
|
||||
const mask = await sharp(path.join(extractedDir, maskFilename))
|
||||
.extractChannel('blue')
|
||||
.resize(imageMeta.width, imageMeta.height)
|
||||
.toBuffer()
|
||||
|
||||
return sharp(imageBuffer).joinChannel(mask).toBuffer()
|
||||
}
|
||||
|
||||
export const crop = async (buffer, rect) => {
|
||||
const left = rect.y
|
||||
const top = rect.x
|
||||
const width = rect.h
|
||||
const height = rect.w
|
||||
const rotate = rect.rotate === 0 ? -90 : 0
|
||||
const newImage = await sharp(buffer)
|
||||
.rotate(90)
|
||||
.extract({ left: left, top: top, width: width, height: height })
|
||||
.resize(width, height)
|
||||
.extract({ left: 0, top: 0, width: width, height: height })
|
||||
.toBuffer()
|
||||
return await sharp(newImage).rotate(rotate).toBuffer()
|
||||
}
|
||||
|
||||
export const toBuffer = async (filename, extractedDir) => {
|
||||
const file = path.join(extractedDir, filename)
|
||||
const { data, info } = await sharp(file)
|
||||
.raw()
|
||||
.toBuffer({ resolveWithObject: true })
|
||||
const { width, height, channels } = info
|
||||
const pixelArray = new Uint8ClampedArray(data.buffer)
|
||||
for (let i = 0; i < pixelArray.length; i += 4) {
|
||||
let alpha = pixelArray[i + 3] / 255
|
||||
pixelArray[i + 0] = pixelArray[i + 0] * alpha
|
||||
pixelArray[i + 1] = pixelArray[i + 1] * alpha
|
||||
pixelArray[i + 2] = pixelArray[i + 2] * alpha
|
||||
}
|
||||
|
||||
return await sharp(pixelArray, { raw: { width, height, channels } })
|
||||
.png()
|
||||
.toBuffer()
|
||||
}
|
||||
7
packages/libs/libs/env.js
Normal file
7
packages/libs/libs/env.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export function generate(values) {
|
||||
return values
|
||||
.map((value) => {
|
||||
return `VITE_${value.key.toUpperCase()}=${value.value}`
|
||||
})
|
||||
.join('\n')
|
||||
}
|
||||
43
packages/libs/libs/env_parser.js
Normal file
43
packages/libs/libs/env_parser.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import process from 'node:process'
|
||||
|
||||
export const parse = (args) => {
|
||||
const envVars = process.env
|
||||
const argKeys = Object.keys(args)
|
||||
const values = {}
|
||||
argKeys.map((key) => {
|
||||
let noInput = false
|
||||
let value,
|
||||
type = args[key].type || 'string',
|
||||
defaultVal = args[key].default,
|
||||
multiple = args[key].multiple || false,
|
||||
short = args[key].short
|
||||
value = envVars[key] || envVars[short]
|
||||
if (!value) noInput = true
|
||||
value = noInput ? defaultVal : value
|
||||
if (noInput) {
|
||||
values[key] = value
|
||||
} else {
|
||||
value = multiple ? value.split(',') : value
|
||||
if (multiple) {
|
||||
values[key] = []
|
||||
value.map((item) => {
|
||||
values[key].push(typeCast(type, item))
|
||||
})
|
||||
} else {
|
||||
values[key] = typeCast(type, value)
|
||||
}
|
||||
}
|
||||
})
|
||||
return values
|
||||
}
|
||||
|
||||
const typeCast = (type, value) => {
|
||||
switch (type) {
|
||||
case 'number':
|
||||
return Number(value)
|
||||
case 'boolean':
|
||||
return Boolean(value)
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
6
packages/libs/libs/error.js
Normal file
6
packages/libs/libs/error.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export const handle = (err) => {
|
||||
if (err.length > 0) {
|
||||
const str = `${err.length} error${err.length > 1 ? 's were' : ' was'} found:\n${err.join('\n')}`
|
||||
throw new Error(str)
|
||||
}
|
||||
}
|
||||
166
packages/libs/libs/file.js
Normal file
166
packages/libs/libs/file.js
Normal file
@@ -0,0 +1,166 @@
|
||||
import fs, { promises as fsP } from 'fs'
|
||||
import path from 'path'
|
||||
import yauzl from 'yauzl-promise'
|
||||
import yazl from 'yazl'
|
||||
|
||||
export async function write(content, filePath) {
|
||||
mkdir(path.dirname(filePath))
|
||||
return await fsP.writeFile(filePath, content, { flag: 'w' })
|
||||
}
|
||||
|
||||
export function writeSync(content, filePath) {
|
||||
mkdir(path.dirname(filePath))
|
||||
return fs.writeFileSync(filePath, content, { flag: 'w' })
|
||||
}
|
||||
|
||||
export async function read(filePath, encoding = 'utf8') {
|
||||
return await fsP.readFile(filePath, encoding, { flag: 'r' })
|
||||
}
|
||||
|
||||
export function readSync(filePath, encoding = 'utf8') {
|
||||
if (exists(filePath)) {
|
||||
return fs.readFileSync(filePath, encoding, { flag: 'r' })
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function exists(filePath) {
|
||||
return fs.existsSync(filePath)
|
||||
}
|
||||
|
||||
export function rmdir(dir) {
|
||||
if (exists(dir)) {
|
||||
fs.rmSync(dir, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
export function rm(dir) {
|
||||
if (exists(dir)) {
|
||||
fs.rmSync(dir, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
export function mkdir(dir) {
|
||||
if (!exists(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
export async function copy(
|
||||
sourcePath,
|
||||
targetPath,
|
||||
mode = fs.constants.COPYFILE_FICLONE
|
||||
) {
|
||||
if (!exists(sourcePath)) {
|
||||
console.warn(`Source file ${sourcePath} does not exist.`)
|
||||
return
|
||||
}
|
||||
mkdir(path.dirname(targetPath))
|
||||
return await fsP.copyFile(sourcePath, targetPath, mode)
|
||||
}
|
||||
|
||||
export async function copyDir(
|
||||
sourcePath,
|
||||
targetPath,
|
||||
mode = fs.constants.COPYFILE_FICLONE
|
||||
) {
|
||||
if (!exists(sourcePath)) {
|
||||
console.warn(`Source file ${sourcePath} does not exist.`)
|
||||
return
|
||||
}
|
||||
mkdir(targetPath)
|
||||
return await fsP.cp(sourcePath, targetPath, { recursive: true, mode })
|
||||
}
|
||||
|
||||
export function appendSync(content, filePath) {
|
||||
return fs.appendFileSync(filePath, content, 'utf8')
|
||||
}
|
||||
|
||||
export function readdirSync(dir) {
|
||||
if (!exists(dir)) {
|
||||
console.warn(`Source ${dir} does not exist.`)
|
||||
return []
|
||||
}
|
||||
return fs.readdirSync(dir)
|
||||
}
|
||||
|
||||
export function fileTypeSync(dir) {
|
||||
if (!exists(dir)) {
|
||||
console.warn(`Source ${dir} does not exist.`)
|
||||
return null
|
||||
}
|
||||
return fs.statSync(dir).isDirectory() ? 'dir' : 'file'
|
||||
}
|
||||
|
||||
export const symlink = (source, target) => {
|
||||
if (!exists(source)) {
|
||||
console.warn(`Source ${source} does not exist.`)
|
||||
return
|
||||
}
|
||||
if (exists(target)) {
|
||||
fs.unlinkSync(target)
|
||||
}
|
||||
mkdir(path.dirname(target))
|
||||
const relative = path.relative(path.dirname(target), source)
|
||||
fs.symlinkSync(relative, target)
|
||||
}
|
||||
|
||||
export const symlinkAll = (source, target) => {
|
||||
const files = readdirSync(source)
|
||||
files.map((file) => {
|
||||
symlink(path.join(source, file), path.join(target, file))
|
||||
})
|
||||
}
|
||||
|
||||
export const mv = (source, target) => {
|
||||
if (!exists(source)) {
|
||||
console.warn(`Source file ${source} does not exist.`)
|
||||
return
|
||||
}
|
||||
if (exists(target)) {
|
||||
rmdir(target)
|
||||
}
|
||||
mkdir(target)
|
||||
fs.renameSync(source, target)
|
||||
}
|
||||
|
||||
export const cpSync = (
|
||||
source,
|
||||
target,
|
||||
opts = {
|
||||
dereference: false,
|
||||
}
|
||||
) => {
|
||||
if (!exists(source)) {
|
||||
console.warn(`Source file ${source} does not exist.`)
|
||||
return
|
||||
}
|
||||
if (fs.statSync(source).isFile()) {
|
||||
mkdir(path.dirname(target))
|
||||
} else {
|
||||
mkdir(target)
|
||||
}
|
||||
fs.cpSync(source, target, {
|
||||
recursive: true,
|
||||
dereference: opts.dereference,
|
||||
})
|
||||
}
|
||||
|
||||
export const relative = (source, target) => {
|
||||
if (!exists(source)) {
|
||||
console.warn(`Source file ${source} does not exist.`)
|
||||
return
|
||||
}
|
||||
return path.relative(source, target)
|
||||
}
|
||||
|
||||
export const size = (source) => {
|
||||
if (!exists(source)) {
|
||||
console.warn(`Source file ${source} does not exist.`)
|
||||
return
|
||||
}
|
||||
return fs.statSync(source).size
|
||||
}
|
||||
|
||||
export const unzip = yauzl
|
||||
export const zip = yazl
|
||||
19
packages/libs/libs/yaml.js
Normal file
19
packages/libs/libs/yaml.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { parse } from 'yaml'
|
||||
|
||||
export function read(file_dir, customTags = []) {
|
||||
const include = {
|
||||
identify: (value) => value.startsWith('!include'),
|
||||
tag: '!include',
|
||||
resolve(str) {
|
||||
const dir = path.resolve(path.dirname(file_dir), str)
|
||||
const data = read(dir)
|
||||
return data
|
||||
},
|
||||
}
|
||||
const file = fs.readFileSync(file_dir, 'utf8')
|
||||
return parse(file, {
|
||||
customTags: [include, ...customTags],
|
||||
})
|
||||
}
|
||||
18
packages/libs/package.json
Normal file
18
packages/libs/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@aklive2d/libs",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@aklive2d/eslint-config": "workspace:*",
|
||||
"@aklive2d/prettier-config": "workspace:*",
|
||||
"sharp": "^0.33.5",
|
||||
"yaml": "^2.7.0",
|
||||
"yauzl-promise": "^4.0.0",
|
||||
"yazl": "^3.3.1"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check ."
|
||||
}
|
||||
}
|
||||
11
packages/libs/prettier.config.js
Normal file
11
packages/libs/prettier.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import baseConfig from '@aklive2d/prettier-config'
|
||||
|
||||
/**
|
||||
* @type {import("prettier").Config}
|
||||
*/
|
||||
const config = {
|
||||
...baseConfig,
|
||||
semi: false,
|
||||
}
|
||||
|
||||
export default config
|
||||
1
packages/music/.gitignore
vendored
Normal file
1
packages/music/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
data
|
||||
3
packages/music/.prettierignore
Normal file
3
packages/music/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
dist
|
||||
data
|
||||
auto_update
|
||||
1
packages/music/auto_update/audio_data_1740038433000.json
Normal file
1
packages/music/auto_update/audio_data_1740038433000.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
packages/music/auto_update/music_table.json
Normal file
1
packages/music/auto_update/music_table.json
Normal file
@@ -0,0 +1 @@
|
||||
[{"id":"bg_rhodes_day","intro":"Audio/Sound_Beta_2/Music/beta1_180603/m_sys_void_intro","loop":"Audio/Sound_Beta_2/Music/beta1_180603/m_sys_void_loop"},{"id":"bg_rhodes_night","intro":"Audio/Sound_Beta_2/Music/beta2_180603/m_sys_tech_intro","loop":"Audio/Sound_Beta_2/Music/beta2_180603/m_sys_tech_loop"},{"id":"bg_main_victoria_1","intro":"Audio/Sound_Beta_2/Music/AVG/m_avg_ghosthunter_intro","loop":"Audio/Sound_Beta_2/Music/AVG/m_avg_ghosthunter_loop"},{"id":"bg_siesta_1","intro":"Audio/Sound_Beta_2/Music/obt/m_sys_ddd_intro","loop":"Audio/Sound_Beta_2/Music/obt/m_sys_ddd_loop"},{"id":"bg_kazimierz_1","intro":"Audio/Sound_Beta_2/Music/beta3_181101/m_dia_street_intro","loop":"Audio/Sound_Beta_2/Music/beta3_181101/m_dia_street_loop"},{"id":"bg_ursus_1","intro":"Audio/Sound_Beta_2/Music/static/m_avg/m_avg_loneliness_intro","loop":"Audio/Sound_Beta_2/Music/static/m_avg/m_avg_loneliness_loop"},{"id":"bg_yan_1","intro":null,"loop":"Audio/Sound_Beta_2/Music/act15side/m_sys_bitw_loop"},{"id":"bg_iberia_1","intro":"Audio/Sound_Beta_2/Music/act18d3d0/m_sys_act18d3d0_intro","loop":"Audio/Sound_Beta_2/Music/act18d3d0/m_sys_act18d3d0_loop"},{"id":"bg_anniversary_1","intro":"Audio/Sound_Beta_2/Music/beta2_180603/m_dia_nightoflongmen_intro","loop":"Audio/Sound_Beta_2/Music/beta2_180603/m_dia_nightoflongmen_loop"},{"id":"bg_rogue_1","intro":null,"loop":"Audio/Sound_Beta_2/Music/rogue_1/m_avg_rglk1secretevent_loop"},{"id":"bg_rogue_2","intro":null,"loop":"Audio/Sound_Beta_2/Music/rogue_2/m_sys_rglk2DLC_loop"},{"id":"bg_laterano_1","intro":"Audio/Sound_Beta_2/Music/act16side/m_sys_act16side_intro","loop":"Audio/Sound_Beta_2/Music/act16side/m_sys_act16side_loop"},{"id":"bg_rhine_1","intro":"Audio/Sound_Beta_2/Music/act19side/m_sys_act19side_intro","loop":"Audio/Sound_Beta_2/Music/act19side/m_sys_act19side_loop"},{"id":"bg_kalts_1","intro":"Audio/Sound_Beta_2/Music/act18d0d0/m_sys_act18d0d0_intro","loop":"Audio/Sound_Beta_2/Music/act18d0d0/m_sys_act18d0d0_loop"},{"id":"bg_rogue_3","intro":"Audio/Sound_Beta_2/Music/rogue_3/m_bat_rglk3DLC_intro","loop":"Audio/Sound_Beta_2/Music/rogue_3/m_bat_rglk3DLC_loop"},{"id":"bg_rainbowsix_1","intro":null,"loop":"Audio/Sound_Beta_2/Music/act32side/m_act32side_sys_loop"},{"id":"bg_rhodes_flower_1","intro":"Audio/Sound_Beta_2/Music/act16mini/m_sys_act16mini_intro","loop":"Audio/Sound_Beta_2/Music/act16mini/m_sys_act16mini_loop"},{"id":"bg_sanrio_1","intro":null,"loop":"Audio/Sound_Beta_2/Music/act27side/m_avg_SiestaCity"},{"id":"bg_sandboxv2_1","intro":null,"loop":"Audio/Sound_Beta_2/Music/sandbox_1/m_sys_sandbox_1_map_loop"},{"id":"bg_dungeon_1","intro":null,"loop":"Audio/Sound_Beta_2/Music/act36side/m_sys_act36side_loop"},{"id":"bg_sui_1","intro":"Audio/Sound_Beta_2/Music/act31side/m_act31side_sys_intro","loop":"Audio/Sound_Beta_2/Music/act31side/m_act31side_sys_loop"},{"id":"bg_rogue_4","intro":null,"loop":"Audio/Sound_Beta_2/Music/act17mini/m_sys_act17mini_loop"},{"id":"operator_bg","intro":"m_sys_void_intro","loop":"m_sys_void_loop"}]
|
||||
3
packages/music/eslint.config.js
Normal file
3
packages/music/eslint.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import baseConfig from '@aklive2d/eslint-config'
|
||||
/** @type {import('eslint').Config} */
|
||||
export default [...baseConfig]
|
||||
117
packages/music/index.js
Normal file
117
packages/music/index.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import path from 'path'
|
||||
import { file } from '@aklive2d/libs'
|
||||
import { githubDownload } from '@aklive2d/downloader'
|
||||
import config from '@aklive2d/config'
|
||||
|
||||
const AUTO_UPDATE_FOLDER = path.resolve(
|
||||
import.meta.dirname,
|
||||
config.dir_name.auto_update
|
||||
)
|
||||
export const DATA_DIR = path.resolve(import.meta.dirname, config.dir_name.data)
|
||||
const MUSIC_TABLE_JSON = path.join(
|
||||
AUTO_UPDATE_FOLDER,
|
||||
config.module.music.music_table_json
|
||||
)
|
||||
|
||||
const download = async () => {
|
||||
const display_meta_table_json = path.resolve(
|
||||
AUTO_UPDATE_FOLDER,
|
||||
config.module.music.display_meta_table_json
|
||||
)
|
||||
const audio_data_json = path.resolve(
|
||||
AUTO_UPDATE_FOLDER,
|
||||
config.module.music.audio_data_json
|
||||
)
|
||||
const metaTable = await githubDownload(
|
||||
`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`,
|
||||
display_meta_table_json
|
||||
)
|
||||
const audioDataTable = await githubDownload(
|
||||
`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`,
|
||||
audio_data_json
|
||||
)
|
||||
return {
|
||||
metaTable,
|
||||
audioDataTable,
|
||||
}
|
||||
}
|
||||
|
||||
const generateMapping = () => {
|
||||
const musicFolder = DATA_DIR
|
||||
const musicTable = JSON.parse(file.readSync(MUSIC_TABLE_JSON))
|
||||
const musicFileMapping = {}
|
||||
const musicFiles = []
|
||||
|
||||
if (!musicTable) return
|
||||
for (const item of musicTable) {
|
||||
const key = `${item.id}.png`
|
||||
musicFileMapping[key] = {}
|
||||
|
||||
if (item.intro) {
|
||||
const filename = `${item.intro.split('/').pop()}.ogg`
|
||||
musicFileMapping[key].intro = filename
|
||||
musicFiles.push({
|
||||
filename,
|
||||
source: musicFolder,
|
||||
})
|
||||
} else {
|
||||
musicFileMapping[key].intro = null
|
||||
}
|
||||
|
||||
if (item.loop) {
|
||||
const filename = `${item.loop.split('/').pop()}.ogg`
|
||||
musicFileMapping[key].loop = filename
|
||||
musicFiles.push({
|
||||
filename,
|
||||
source: musicFolder,
|
||||
})
|
||||
} else {
|
||||
musicFileMapping[key].loop = null
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
musicFiles,
|
||||
musicFileMapping,
|
||||
}
|
||||
}
|
||||
|
||||
export const mapping = generateMapping()
|
||||
|
||||
export const update = async () => {
|
||||
const { metaTable, audioDataTable } = await download()
|
||||
const musicTable = audioDataTable.musics
|
||||
const musicBank = audioDataTable.bgmBanks
|
||||
const musicBankAlias = audioDataTable.bankAlias
|
||||
const musicData = metaTable.homeBackgroundData.homeBgDataList.reduce(
|
||||
(acc, cur) => {
|
||||
acc.push({
|
||||
id: cur.bgId,
|
||||
musicId: cur.bgMusicId,
|
||||
})
|
||||
return acc
|
||||
},
|
||||
[]
|
||||
)
|
||||
const list = []
|
||||
for (const item of musicData) {
|
||||
let bankName = musicTable.find((el) => item.musicId === el.id).bank
|
||||
if (typeof musicBankAlias[bankName] !== 'undefined') {
|
||||
bankName = musicBankAlias[bankName]
|
||||
}
|
||||
const obj = musicBank.find((el) => bankName === el.name)
|
||||
list.push({
|
||||
id: item.id,
|
||||
intro: obj.intro,
|
||||
loop: obj.loop,
|
||||
})
|
||||
}
|
||||
list.push({
|
||||
id: 'operator_bg',
|
||||
intro: 'm_sys_void_intro',
|
||||
loop: 'm_sys_void_loop',
|
||||
})
|
||||
file.writeSync(JSON.stringify(list, null), MUSIC_TABLE_JSON)
|
||||
}
|
||||
18
packages/music/package.json
Normal file
18
packages/music/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@aklive2d/music",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@aklive2d/libs": "workspace:*",
|
||||
"@aklive2d/config": "workspace:*",
|
||||
"@aklive2d/downloader": "workspace:*",
|
||||
"@aklive2d/eslint-config": "workspace:*",
|
||||
"@aklive2d/prettier-config": "workspace:*"
|
||||
},
|
||||
"scripts": {
|
||||
"update": "mode=update node runner.js",
|
||||
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check ."
|
||||
}
|
||||
}
|
||||
11
packages/music/prettier.config.js
Normal file
11
packages/music/prettier.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import baseConfig from '@aklive2d/prettier-config'
|
||||
|
||||
/**
|
||||
* @type {import("prettier").Config}
|
||||
*/
|
||||
const config = {
|
||||
...baseConfig,
|
||||
semi: false,
|
||||
}
|
||||
|
||||
export default config
|
||||
20
packages/music/runner.js
Normal file
20
packages/music/runner.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { envParser } from '@aklive2d/libs'
|
||||
import { update } from './index.js'
|
||||
|
||||
async function main() {
|
||||
const { mode } = envParser.parse({
|
||||
mode: {
|
||||
type: 'string',
|
||||
short: 'm',
|
||||
},
|
||||
})
|
||||
switch (mode) {
|
||||
case 'update':
|
||||
await update()
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unknown mode: ${mode}`)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
3
packages/official-info/.prettierignore
Normal file
3
packages/official-info/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
dist
|
||||
data
|
||||
auto_update
|
||||
635
packages/official-info/auto_update/official_info.json
Normal file
635
packages/official-info/auto_update/official_info.json
Normal file
@@ -0,0 +1,635 @@
|
||||
{
|
||||
"length": 60,
|
||||
"dates": [
|
||||
"2025-01-01",
|
||||
"2024-12-09",
|
||||
"2024-12-01",
|
||||
"2024-10-01",
|
||||
"2024-07-01",
|
||||
"2024-05-01",
|
||||
"2024-04-01",
|
||||
"2024-02-29",
|
||||
"2024-02-28",
|
||||
"2024-01-01",
|
||||
"2023-11-01",
|
||||
"2023-08-01",
|
||||
"2023-07-01",
|
||||
"2023-04-01",
|
||||
"2023-02-01",
|
||||
"2023-01-01",
|
||||
"2022-12-15",
|
||||
"2022-11-01",
|
||||
"2022-10-01",
|
||||
"2022-08-01",
|
||||
"2022-05-01",
|
||||
"2022-04-30",
|
||||
"2022-01-01",
|
||||
"2021-11-01",
|
||||
"2021-08-01",
|
||||
"2021-05-01",
|
||||
"2021-02-01",
|
||||
"2020-11-01",
|
||||
"2020-05-01",
|
||||
"2020-01-01"
|
||||
],
|
||||
"2025-01-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "少年游 · 左乐",
|
||||
"en-US": "Youthful Journey"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202501927.html",
|
||||
"id": "202501927"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "春日宴 · 黍",
|
||||
"en-US": "Spring Feast"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202501936.html",
|
||||
"id": "202501936"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "余",
|
||||
"en-US": "yu"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202501414.html",
|
||||
"id": "202501414"
|
||||
}
|
||||
],
|
||||
"2024-12-09": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "霹雳导演 · 年",
|
||||
"en-US": "Thunderbolt Director"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202412491.html",
|
||||
"id": "202412491"
|
||||
}
|
||||
],
|
||||
"2024-12-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "寰宇独奏 · 阿米娅",
|
||||
"en-US": "Solo Around The World"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202412473.html",
|
||||
"id": "202412473"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "全能演员 · 重岳",
|
||||
"en-US": "All-Round Actor"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202412452.html",
|
||||
"id": "202412452"
|
||||
}
|
||||
],
|
||||
"2024-10-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "荒芜拉普兰德",
|
||||
"en-US": "Lappland the Decadenza"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202410440.html",
|
||||
"id": "202410440"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "幽兰秘辛 · 缄默德克萨斯",
|
||||
"en-US": "Il Segreto Della Notte"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202410409.html",
|
||||
"id": "202410409"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "众志归一 · 圣约送葬人",
|
||||
"en-US": "Allmind as one"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202410488.html",
|
||||
"id": "202410488"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "无我唯识 · 塑心",
|
||||
"en-US": "Diversity Oneness"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202410467.html",
|
||||
"id": "202410467"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "暗月的影子 · 锏",
|
||||
"en-US": "The Shadow Of The Dark Moon"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202410426.html",
|
||||
"id": "202410426"
|
||||
}
|
||||
],
|
||||
"2024-07-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "流辉 · 夜莺",
|
||||
"en-US": "Iakhu of Flows"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202407435.html",
|
||||
"id": "202407435"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "佩佩",
|
||||
"en-US": "pepe"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202407013.html",
|
||||
"id": "202407013"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "远行前的野餐 · 纯烬艾雅法拉",
|
||||
"en-US": "A Picnic Before A Long Trip"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202407072.html",
|
||||
"id": "202407072"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "夏卉 FA075 · 焰影苇草",
|
||||
"en-US": "Summer Flowers FA075"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202407051.html",
|
||||
"id": "202407051"
|
||||
}
|
||||
],
|
||||
"2024-05-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "维什戴尔",
|
||||
"en-US": "WISADEL"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202404049.html",
|
||||
"id": "202404049"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "新枝 · 缪尔赛思",
|
||||
"en-US": "Muelsyse"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202404090.html",
|
||||
"id": "202404090"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "红女爵 · 浊心斯卡蒂",
|
||||
"en-US": "Red Countess"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202404008.html",
|
||||
"id": "202404008"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "燃烧天穹下 · 伊内丝",
|
||||
"en-US": "INES"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202404087.html",
|
||||
"id": "202404087"
|
||||
}
|
||||
],
|
||||
"2024-04-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "不融冰 · 银灰",
|
||||
"en-US": "Never-melting Ice"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202404066.html",
|
||||
"id": "202404066"
|
||||
}
|
||||
],
|
||||
"2024-02-29": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "黍",
|
||||
"en-US": "shu"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202401025.html",
|
||||
"id": "202401025"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "列瑶台 · 林",
|
||||
"en-US": "Heavenly Mirage"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202401034.html",
|
||||
"id": "202401034"
|
||||
}
|
||||
],
|
||||
"2024-02-28": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "何处栖 · 重岳",
|
||||
"en-US": "Alighting"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202401812.html",
|
||||
"id": "202401812"
|
||||
}
|
||||
],
|
||||
"2024-01-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "博物 · 焰影苇草",
|
||||
"en-US": "Curator"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202401871.html",
|
||||
"id": "202401871"
|
||||
}
|
||||
],
|
||||
"2023-11-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "塑心",
|
||||
"en-US": "Virtuosa"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202310848.html",
|
||||
"id": "202310848"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "远路 · 玛恩纳",
|
||||
"en-US": "W Dali"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202310850.html",
|
||||
"id": "202310850"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "破翼者 · 缄默德克萨斯",
|
||||
"en-US": "Wingbreaker"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202310899.html",
|
||||
"id": "202310899"
|
||||
}
|
||||
],
|
||||
"2023-08-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "崖高梦远 · 令",
|
||||
"en-US": "Towering is cliff of nostalgia"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202308807.html",
|
||||
"id": "202308807"
|
||||
}
|
||||
],
|
||||
"2023-07-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "悠然假日 HD26 · 百炼嘉维尔",
|
||||
"en-US": "Gavial the Invincible HD26"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202307886.html",
|
||||
"id": "202307886"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "纯烬艾雅法拉",
|
||||
"en-US": "Eyjafjalla the Hvít Aska"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202307865.html",
|
||||
"id": "202307865"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "夏卉 FA394 · 澄闪",
|
||||
"en-US": "Summer Flowers FA394"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202307824.html",
|
||||
"id": "202307824"
|
||||
}
|
||||
],
|
||||
"2023-04-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "残余 · 凯尔希",
|
||||
"en-US": "Remnant"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202304833.html",
|
||||
"id": "202304833"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "缪尔赛思",
|
||||
"en-US": "Muelsyse"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202304611.html",
|
||||
"id": "202304611"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "生而为一 · 归溟幽灵鲨",
|
||||
"en-US": "Born as One"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202304670.html",
|
||||
"id": "202304670"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "万重山 · 假日威龙陈",
|
||||
"en-US": "Ten Thousand Mountains"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202304659.html",
|
||||
"id": "202304659"
|
||||
}
|
||||
],
|
||||
"2023-02-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "字句中的雪原 · 鸿雪",
|
||||
"en-US": "Snowy Plains in Words"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202302698.html",
|
||||
"id": "202302698"
|
||||
}
|
||||
],
|
||||
"2023-01-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "重岳",
|
||||
"en-US": "Chongyue"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202301606.html",
|
||||
"id": "202301606"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "濯缨 · 令",
|
||||
"en-US": "It Does Wash the Strings"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202301647.html",
|
||||
"id": "202301647"
|
||||
}
|
||||
],
|
||||
"2022-12-15": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "夏日餮宴 · 水月",
|
||||
"en-US": "Summer Feast"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202211685.html",
|
||||
"id": "202211685"
|
||||
}
|
||||
],
|
||||
"2022-11-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "缄默德克萨斯",
|
||||
"en-US": "Texas the Omertosa"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202210210.html",
|
||||
"id": "202210210"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "今昔须臾之梦 · 异客",
|
||||
"en-US": "Dream in a Moment"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202210664.html",
|
||||
"id": "202210664"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "复现荣光 · 耀骑士临光",
|
||||
"en-US": "Relight"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202210623.html",
|
||||
"id": "202210623"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "拥抱新生 · 迷迭香",
|
||||
"en-US": "Become Anew"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202210632.html",
|
||||
"id": "202210632"
|
||||
}
|
||||
],
|
||||
"2022-10-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "手到牌来 · 老鲤",
|
||||
"en-US": "Trust Your Eyes"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202210279.html",
|
||||
"id": "202210279"
|
||||
}
|
||||
],
|
||||
"2022-08-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "百炼嘉维尔 ",
|
||||
"en-US": "Gavial the Invincible"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202208258.html",
|
||||
"id": "202208258"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "缤纷奇境 CW03 · 史尔特尔",
|
||||
"en-US": "Colorful Wonderland CW03"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202208297.html",
|
||||
"id": "202208297"
|
||||
}
|
||||
],
|
||||
"2022-05-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "归溟幽灵鲨",
|
||||
"en-US": "SPECTER THE UNCHAINED"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202204284.html",
|
||||
"id": "202204284"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "升华 · 浊心斯卡蒂",
|
||||
"en-US": "SUBLIMATION"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202204205.html",
|
||||
"id": "202204205"
|
||||
}
|
||||
],
|
||||
"2022-04-30": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "焦点 · 傀影",
|
||||
"en-US": "FOCUS"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202203222.html",
|
||||
"id": "202203222"
|
||||
}
|
||||
],
|
||||
"2022-01-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "令",
|
||||
"en-US": "LING"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/20220383.html",
|
||||
"id": "20220383"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "染尘烟 · 夕",
|
||||
"en-US": "EVERYTHING IS A MIRACLE"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/20220321.html",
|
||||
"id": "20220321"
|
||||
}
|
||||
],
|
||||
"2021-11-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "耀骑士临光",
|
||||
"en-US": "NEARL THE RADIANT KNIGHT"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/20220304.html",
|
||||
"id": "20220304"
|
||||
}
|
||||
],
|
||||
"2021-08-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "假日威龙陈",
|
||||
"en-US": "Ch'en the Holungday"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/20220345.html",
|
||||
"id": "20220345"
|
||||
}
|
||||
],
|
||||
"2021-05-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "浊心斯卡蒂",
|
||||
"en-US": "SKADI THE CORRUPTING HEART"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/20220396.html",
|
||||
"id": "20220396"
|
||||
}
|
||||
],
|
||||
"2021-02-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "夕",
|
||||
"en-US": "DUSK"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202203263.html",
|
||||
"id": "202203263"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "乐逍遥 · 年",
|
||||
"en-US": "UNFETTERED FREEDOM"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/20220362.html",
|
||||
"id": "20220362"
|
||||
}
|
||||
],
|
||||
"2020-11-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "迷迭香",
|
||||
"en-US": "ROSMONTIS"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/20220378.html",
|
||||
"id": "20220378"
|
||||
},
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "恍惚 · W",
|
||||
"en-US": "Wonder"
|
||||
},
|
||||
"type": "skin",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202206246.html",
|
||||
"id": "202206246"
|
||||
}
|
||||
],
|
||||
"2020-05-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "W",
|
||||
"en-US": "W"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/20220319.html",
|
||||
"id": "20220319"
|
||||
}
|
||||
],
|
||||
"2020-01-01": [
|
||||
{
|
||||
"codename": {
|
||||
"zh-CN": "年",
|
||||
"en-US": "NIAN"
|
||||
},
|
||||
"type": "operator",
|
||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202203231.html",
|
||||
"id": "202203231"
|
||||
}
|
||||
]
|
||||
}
|
||||
3
packages/official-info/eslint.config.js
Normal file
3
packages/official-info/eslint.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import baseConfig from '@aklive2d/eslint-config'
|
||||
/** @type {import('eslint').Config} */
|
||||
export default [...baseConfig]
|
||||
101
packages/official-info/index.js
Normal file
101
packages/official-info/index.js
Normal file
@@ -0,0 +1,101 @@
|
||||
import jsdom from 'jsdom'
|
||||
import path from 'path'
|
||||
import { file } from '@aklive2d/libs'
|
||||
import config from '@aklive2d/config'
|
||||
|
||||
const AUTO_UPDATE_FOLDER = path.resolve(
|
||||
import.meta.dirname,
|
||||
config.dir_name.auto_update
|
||||
)
|
||||
const OFFICIAL_INFO_JSON = path.resolve(
|
||||
AUTO_UPDATE_FOLDER,
|
||||
config.module.official_info.official_info_json
|
||||
)
|
||||
|
||||
export const update = async () => {
|
||||
const f = await fetch('https://ak.hypergryph.com/archive/dynamicCompile/')
|
||||
const html_text = await f.text()
|
||||
|
||||
const dom = new jsdom.JSDOM(html_text)
|
||||
const scripts = dom.window.document.body.querySelectorAll('script')
|
||||
let data
|
||||
scripts.forEach((e) => {
|
||||
if (e.textContent.includes('干员晋升')) {
|
||||
data = JSON.parse(
|
||||
e.textContent
|
||||
.replace('self.__next_f.push([1,"c:', '')
|
||||
.replace('\\n"])', '')
|
||||
.replaceAll('\\', '')
|
||||
)
|
||||
}
|
||||
})
|
||||
const rows = data[0][3].initialData
|
||||
|
||||
const dict = {
|
||||
length: rows.length,
|
||||
dates: [],
|
||||
}
|
||||
|
||||
let current_displayTime = rows[0].displayTime
|
||||
let current_block = []
|
||||
|
||||
for (const row of rows) {
|
||||
const displayTime = row.displayTime
|
||||
if (displayTime !== current_displayTime) {
|
||||
dict[current_displayTime] = current_block
|
||||
dict.dates.push(current_displayTime)
|
||||
current_displayTime = row.displayTime
|
||||
current_block = []
|
||||
}
|
||||
current_block.push(get_row(row))
|
||||
}
|
||||
dict[current_displayTime] = current_block
|
||||
dict.dates.push(current_displayTime)
|
||||
|
||||
file.writeSync(JSON.stringify(dict, null, 4), OFFICIAL_INFO_JSON)
|
||||
}
|
||||
|
||||
const get_row = (row) => {
|
||||
const type = row.type
|
||||
let codename_zhCN, item_type
|
||||
switch (type) {
|
||||
case 0:
|
||||
codename_zhCN = row.charName
|
||||
item_type = 'operator'
|
||||
break
|
||||
case 1:
|
||||
codename_zhCN = row.suitName + ' · ' + row.charName
|
||||
item_type = 'skin'
|
||||
break
|
||||
default:
|
||||
throw 'unknown type'
|
||||
}
|
||||
return {
|
||||
codename: {
|
||||
'zh-CN': codename_zhCN,
|
||||
'en-US': row.codename,
|
||||
},
|
||||
type: item_type,
|
||||
link: `https://ak.hypergryph.com/archive/dynamicCompile/${row.cid}.html`,
|
||||
id: row.cid,
|
||||
}
|
||||
}
|
||||
|
||||
const generateMapping = () => {
|
||||
const mapping = {}
|
||||
const data = JSON.parse(file.readSync(OFFICIAL_INFO_JSON))
|
||||
if (!data) return
|
||||
Object.keys(data).forEach((key) => {
|
||||
if (typeof data[key] === 'object') {
|
||||
data[key].forEach((operator) => {
|
||||
mapping[operator.id] = {
|
||||
date: key,
|
||||
...operator,
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
return mapping
|
||||
}
|
||||
|
||||
export const mapping = generateMapping()
|
||||
18
packages/official-info/package.json
Normal file
18
packages/official-info/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@aklive2d/official-info",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"jsdom": "^26.0.0",
|
||||
"@aklive2d/libs": "workspace:*",
|
||||
"@aklive2d/config": "workspace:*",
|
||||
"@aklive2d/eslint-config": "workspace:*",
|
||||
"@aklive2d/prettier-config": "workspace:*"
|
||||
},
|
||||
"scripts": {
|
||||
"update": "mode=update node runner.js",
|
||||
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check ."
|
||||
}
|
||||
}
|
||||
11
packages/official-info/prettier.config.js
Normal file
11
packages/official-info/prettier.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import baseConfig from '@aklive2d/prettier-config'
|
||||
|
||||
/**
|
||||
* @type {import("prettier").Config}
|
||||
*/
|
||||
const config = {
|
||||
...baseConfig,
|
||||
semi: false,
|
||||
}
|
||||
|
||||
export default config
|
||||
20
packages/official-info/runner.js
Normal file
20
packages/official-info/runner.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { envParser } from '@aklive2d/libs'
|
||||
import { update } from './index.js'
|
||||
|
||||
async function main() {
|
||||
const { mode } = envParser.parse({
|
||||
mode: {
|
||||
type: 'string',
|
||||
short: 'm',
|
||||
},
|
||||
})
|
||||
switch (mode) {
|
||||
case 'update':
|
||||
await update()
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unknown mode: ${mode}`)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
1
packages/operator/.gitignore
vendored
Normal file
1
packages/operator/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
data
|
||||
3
packages/operator/.prettierignore
Normal file
3
packages/operator/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
dist
|
||||
data
|
||||
auto_update
|
||||
60
packages/operator/config.yaml
Normal file
60
packages/operator/config.yaml
Normal file
@@ -0,0 +1,60 @@
|
||||
chen: !include config/chen.yaml
|
||||
dusk: !include config/dusk.yaml
|
||||
dusk_everything_is_a_miracle: !include config/dusk_everything_is_a_miracle.yaml
|
||||
ling: !include config/ling.yaml
|
||||
nearl: !include config/nearl.yaml
|
||||
nian: !include config/nian.yaml
|
||||
nian_unfettered_freedom: !include config/nian_unfettered_freedom.yaml
|
||||
phatom_focus: !include config/phatom_focus.yaml
|
||||
rosmontis: !include config/rosmontis.yaml
|
||||
skadi: !include config/skadi.yaml
|
||||
skadi_sublimation: !include config/skadi_sublimation.yaml
|
||||
w: !include config/w.yaml
|
||||
w_wonder: !include config/w_wonder.yaml
|
||||
specter: !include config/specter.yaml
|
||||
gavial: !include config/gavial.yaml
|
||||
surtr_colorful_wonderland: !include config/surtr_colorful_wonderland.yaml
|
||||
lee_trust_your_eyes: !include config/lee_trust_your_eyes.yaml
|
||||
texas_the_omertosa: !include config/texas_the_omertosa.yaml
|
||||
nearl_relight: !include config/nearl_relight.yaml
|
||||
rosmontis_become_anew: !include config/rosmontis_become_anew.yaml
|
||||
passager_dream_in_a_moment: !include config/passager_dream_in_a_moment.yaml
|
||||
mizuki_summer_feast: !include config/mizuki_summer_feast.yaml
|
||||
chongyue: !include config/chongyue.yaml
|
||||
ling_it_does_wash_the_strings: !include config/ling_it_does_wash_the_strings.yaml
|
||||
pozemka_snowy_plains_in_words: !include config/pozemka_snowy_plains_in_words.yaml
|
||||
chen_ten_thousand_mountains: !include config/chen_ten_thousand_mountains.yaml
|
||||
specter_born_as_one: !include config/specter_born_as_one.yaml
|
||||
muelsyse: !include config/muelsyse.yaml
|
||||
kaltsit_remnant: !include config/kaltsit_remnant.yaml
|
||||
eyjafjalla_the_hvit_aska: !include config/eyjafjalla_the_hvit_aska.yaml
|
||||
goldenglow_summer_flowers_fa394: !include config/goldenglow_summer_flowers_fa394.yaml
|
||||
gavial_the_invincible_holiday_hd26: !include config/gavial_the_invincible_holiday_hd26.yaml
|
||||
ling_towering_is_cliff_of_nostalgia: !include config/ling_towering_is_cliff_of_nostalgia.yaml
|
||||
virtuosa: !include config/virtuosa.yaml
|
||||
texas_the_omertosa_wingbreaker: !include config/texas_the_omertosa_wingbreaker.yaml
|
||||
mwynar_w_dali: !include config/mwynar_w_dali.yaml
|
||||
reed_the_frame_shadow_curator: !include config/reed_the_frame_shadow_curator.yaml
|
||||
shu: !include config/shu.yaml
|
||||
lin_heavenly_mirage: !include config/lin_heavenly_mirage.yaml
|
||||
chongyue_alighting: !include config/chongyue_alighting.yaml
|
||||
wisadel: !include config/wisadel.yaml
|
||||
muelsyse_young_branch: !include config/muelsyse_young_branch.yaml
|
||||
skadi_the_corrupting_heart_red_countess: !include config/skadi_the_corrupting_heart_red_countess.yaml
|
||||
ines_under_the_flaming_dome: !include config/ines_under_the_flaming_dome.yaml
|
||||
silverash_never_melting_ice: !include config/silverash_never_melting_ice.yaml
|
||||
reed_the_frame_shadow_summer_flower: !include config/reed_the_frame_shadow_summer_flower.yaml
|
||||
eyjafjalla_the_hvit_aska_a_picnic_before_a_long_trip: !include config/eyjafjalla_the_hvit_aska_a_picnic_before_a_long_trip.yaml
|
||||
pepe: !include config/pepe.yaml
|
||||
nightingale_iakhu_of_flows: !include config/nightingale_iakhu_of_flows.yaml
|
||||
degenbrecher_the_shadow_of_dark_moon: !include config/degenbrecher_the_shadow_of_dark_moon.yaml
|
||||
lappland_the_decadenza: !include config/lappland_the_decadenza.yaml
|
||||
texas_the_omertosa_il_se_de_no: !include config/texas_the_omertosa_il_se_de_no.yaml
|
||||
executor_the_ex_foedere_allmind_as_one: !include config/executor_the_ex_foedere_allmind_as_one.yaml
|
||||
virtuosa_diversity_oneness: !include config/virtuosa_diversity_oneness.yaml
|
||||
chongyue_allround_actor: !include config/chongyue_allround_actor.yaml
|
||||
nian_thunderbolt_director: !include config/nian_thunderbolt_director.yaml
|
||||
amiya_solo_around_the_world: !include config/amiya_solo_around_the_world.yaml
|
||||
zuole_youthful_journey: !include config/zuole_youthful_journey.yaml
|
||||
shu_spring_feast: !include config/shu_spring_feast.yaml
|
||||
yu: !include config/yu.yaml
|
||||
12
packages/operator/config/_template.yaml
Normal file
12
packages/operator/config/_template.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
filename: dyn_illust_char_1013_chen2
|
||||
logo: logo_rhodes_override
|
||||
fallback_name: char_1013_chen2_2
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 假日威龙陈
|
||||
en-US: Ch'en/Chen the Holungday
|
||||
use_json: false
|
||||
13
packages/operator/config/amiya_solo_around_the_world.yaml
Normal file
13
packages/operator/config/amiya_solo_around_the_world.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_1037_amiya3_sale#13
|
||||
logo: logo_rhodes_override
|
||||
fallback_name: char_1037_amiya3_sale#13
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: false
|
||||
codename:
|
||||
zh-CN: 寰宇独奏 · 阿米娅
|
||||
en-US: Solo Around The World / Amiya
|
||||
use_json: false
|
||||
official_id: '202412473'
|
||||
13
packages/operator/config/chen.yaml
Normal file
13
packages/operator/config/chen.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_1013_chen2
|
||||
logo: logo_rhodes_override
|
||||
fallback_name: char_1013_chen2_2
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 1
|
||||
viewport_bottom: 1
|
||||
invert_filter: false
|
||||
codename:
|
||||
zh-CN: 假日威龙陈
|
||||
en-US: Ch'en/Chen the Holungday
|
||||
use_json: false
|
||||
official_id: '20220345'
|
||||
13
packages/operator/config/chen_ten_thousand_mountains.yaml
Normal file
13
packages/operator/config/chen_ten_thousand_mountains.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_1013_chen2_boc#6
|
||||
logo: logo_rhodes_override
|
||||
fallback_name: char_1013_chen2_boc#6
|
||||
viewport_left: -1
|
||||
viewport_right: 1
|
||||
viewport_top: 2
|
||||
viewport_bottom: -1
|
||||
invert_filter: false
|
||||
codename:
|
||||
zh-CN: 万重山 · 假日威龙陈
|
||||
en-US: Ten Thousand Mountains / Ch'en/Chen the Holungday
|
||||
use_json: false
|
||||
official_id: '202304659'
|
||||
13
packages/operator/config/chongyue.yaml
Normal file
13
packages/operator/config/chongyue.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_2024_chyue
|
||||
logo: logo_sui
|
||||
fallback_name: char_2024_chyue_2
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 重岳
|
||||
en-US: Chongyue
|
||||
use_json: false
|
||||
official_id: '202301606'
|
||||
13
packages/operator/config/chongyue_alighting.yaml
Normal file
13
packages/operator/config/chongyue_alighting.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_2024_chyue_nian#10
|
||||
logo: logo_sui
|
||||
fallback_name: char_2024_chyue_nian#10
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 何处栖 · 重岳
|
||||
en-US: Alighting / Chongyue
|
||||
use_json: false
|
||||
official_id: '202401812'
|
||||
13
packages/operator/config/chongyue_allround_actor.yaml
Normal file
13
packages/operator/config/chongyue_allround_actor.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_2024_chyue_cfa#1
|
||||
logo: logo_sui
|
||||
fallback_name: char_2024_chyue_cfa#1
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 全能演员 · 重岳
|
||||
en-US: All-Round Actor / Chongyue
|
||||
use_json: false
|
||||
official_id: '202412452'
|
||||
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_4116_blkkgt_witch#5
|
||||
logo: logo_kjerag
|
||||
fallback_name: char_4116_blkkgt_witch#5
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 暗月的影子 · 锏
|
||||
en-US: The Shadow Of The Dark Moon / Degenbrecher
|
||||
use_json: false
|
||||
official_id: '202410426'
|
||||
13
packages/operator/config/dusk.yaml
Normal file
13
packages/operator/config/dusk.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_2015_dusk
|
||||
logo: logo_sui
|
||||
fallback_name: char_2015_dusk_2
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 夕
|
||||
en-US: Dusk
|
||||
use_json: false
|
||||
official_id: '202203263'
|
||||
13
packages/operator/config/dusk_everything_is_a_miracle.yaml
Normal file
13
packages/operator/config/dusk_everything_is_a_miracle.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_2015_dusk_nian#7
|
||||
logo: logo_sui
|
||||
fallback_name: char_2015_dusk_nian#7
|
||||
viewport_left: 10
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 染尘烟 · 夕
|
||||
en-US: Everything is a Miracle / Dusk
|
||||
use_json: false
|
||||
official_id: '20220321'
|
||||
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_1032_excu2_sale#12
|
||||
logo: logo_Laterano
|
||||
fallback_name: char_1032_excu2_sale#12
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 众志归一 · 圣约送葬人
|
||||
en-US: Allmind as one / Executor the Ex Foedere
|
||||
use_json: false
|
||||
official_id: '202410488'
|
||||
13
packages/operator/config/eyjafjalla_the_hvit_aska.yaml
Normal file
13
packages/operator/config/eyjafjalla_the_hvit_aska.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_1016_agoat2
|
||||
logo: logo_Leithanien
|
||||
fallback_name: char_1016_agoat2_2
|
||||
viewport_left: 1
|
||||
viewport_right: 0
|
||||
viewport_top: 5
|
||||
viewport_bottom: 5
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 纯烬艾雅法拉
|
||||
en-US: Eyjafjalla the Hvít Aska
|
||||
use_json: false
|
||||
official_id: '202307865'
|
||||
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_1016_agoat2_epoque#34
|
||||
logo: logo_Leithanien
|
||||
fallback_name: char_1016_agoat2_epoque#34
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 远行前的野餐 · 纯烬艾雅法拉
|
||||
en-US: A Picnic Before A Long Trip / Eyjafjalla the Hvít Aska
|
||||
use_json: true
|
||||
official_id: '202407072'
|
||||
13
packages/operator/config/gavial.yaml
Normal file
13
packages/operator/config/gavial.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_1026_gvial2
|
||||
logo: logo_rhodes_override
|
||||
fallback_name: char_1026_gvial2_2
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: false
|
||||
codename:
|
||||
zh-CN: 百练嘉维尔
|
||||
en-US: Gavial the Invincible
|
||||
use_json: false
|
||||
official_id: '202208258'
|
||||
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_1026_gvial2_summer#12
|
||||
logo: logo_rhodes_override
|
||||
fallback_name: char_1026_gvial2_summer#12
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: false
|
||||
codename:
|
||||
zh-CN: 悠然假日 HD26 · 百炼嘉维尔
|
||||
en-US: Holiday HD26 / Gavial the Invincible
|
||||
use_json: false
|
||||
official_id: '202307886'
|
||||
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_377_gdglow_summer#12
|
||||
logo: logo_victoria
|
||||
fallback_name: char_377_gdglow_summer#12
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 夏卉 FA394 · 澄闪
|
||||
en-US: Summer Flowers FA394 / Goldenglow
|
||||
use_json: false
|
||||
official_id: '202307824'
|
||||
13
packages/operator/config/ines_under_the_flaming_dome.yaml
Normal file
13
packages/operator/config/ines_under_the_flaming_dome.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_4087_ines_boc#8
|
||||
logo: logo_babel
|
||||
fallback_name: char_4087_ines_boc#8
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 燃烧天穹下 · 伊内丝
|
||||
en-US: Under the Flaming Dome / Ines
|
||||
use_json: false
|
||||
official_id: '202404087'
|
||||
13
packages/operator/config/kaltsit_remnant.yaml
Normal file
13
packages/operator/config/kaltsit_remnant.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_003_kalts_boc#6
|
||||
logo: logo_rhodes_override
|
||||
fallback_name: char_003_kalts_boc#6
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: false
|
||||
codename:
|
||||
zh-CN: 残余 · 凯尔希
|
||||
en-US: Remnant / Kal'tsit
|
||||
use_json: false
|
||||
official_id: '202304833'
|
||||
13
packages/operator/config/lappland_the_decadenza.yaml
Normal file
13
packages/operator/config/lappland_the_decadenza.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_1038_whitw2
|
||||
logo: logo_chiave
|
||||
fallback_name: char_1038_whitw2_2
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 荒芜拉普兰德
|
||||
en-US: Lappland the Decadenza
|
||||
use_json: false
|
||||
official_id: '202410440'
|
||||
13
packages/operator/config/lee_trust_your_eyes.yaml
Normal file
13
packages/operator/config/lee_trust_your_eyes.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_322_lmlee_witch#3
|
||||
logo: logo_lee
|
||||
fallback_name: char_322_lmlee_witch#3
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 手到牌来 · 老鲤
|
||||
en-US: Trust Your Eyes / Lee
|
||||
use_json: false
|
||||
official_id: '202210279'
|
||||
13
packages/operator/config/lin_heavenly_mirage.yaml
Normal file
13
packages/operator/config/lin_heavenly_mirage.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_4080_lin_nian#10
|
||||
logo: logo_lungmen
|
||||
fallback_name: char_4080_lin_nian#10
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 列瑶台 · 林
|
||||
en-US: Heavenly Mirage / Lin
|
||||
use_json: false
|
||||
official_id: '202401034'
|
||||
13
packages/operator/config/ling.yaml
Normal file
13
packages/operator/config/ling.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_2023_ling
|
||||
logo: logo_sui
|
||||
fallback_name: char_2023_ling_2
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 令
|
||||
en-US: Ling
|
||||
use_json: false
|
||||
official_id: '20220383'
|
||||
13
packages/operator/config/ling_it_does_wash_the_strings.yaml
Normal file
13
packages/operator/config/ling_it_does_wash_the_strings.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_2023_ling_nian#9
|
||||
logo: logo_sui
|
||||
fallback_name: char_2023_ling_nian#9
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 濯缨 · 令
|
||||
en-US: It Does Wash the Strings / Ling
|
||||
use_json: false
|
||||
official_id: '202301647'
|
||||
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_2023_ling_ncg#1
|
||||
logo: logo_sui
|
||||
fallback_name: char_2023_ling_ncg#1
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 崖高梦远 · 令
|
||||
en-US: Towering is Cliff of Nostalgia
|
||||
use_json: false
|
||||
official_id: '202308807'
|
||||
13
packages/operator/config/mizuki_summer_feast.yaml
Normal file
13
packages/operator/config/mizuki_summer_feast.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_437_mizuki_sale#7
|
||||
logo: logo_higashi
|
||||
fallback_name: char_437_mizuki_sale#7
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 夏日餮宴 · 水月
|
||||
en-US: Summer Feast / Mizuki
|
||||
use_json: false
|
||||
official_id: '202211685'
|
||||
13
packages/operator/config/muelsyse.yaml
Normal file
13
packages/operator/config/muelsyse.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_249_mlyss
|
||||
logo: logo_rhine
|
||||
fallback_name: char_249_mlyss_2
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 3
|
||||
viewport_bottom: 2
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 缪尔赛思
|
||||
en-US: Muelsyse
|
||||
use_json: false
|
||||
official_id: '202304611'
|
||||
13
packages/operator/config/muelsyse_young_branch.yaml
Normal file
13
packages/operator/config/muelsyse_young_branch.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_249_mlyss_boc#8
|
||||
logo: logo_rhine
|
||||
fallback_name: char_249_mlyss_boc#8
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 新枝 · 缪尔赛思
|
||||
en-US: Young Branch / Muelsyse
|
||||
use_json: false
|
||||
official_id: '202404090'
|
||||
13
packages/operator/config/mwynar_w_dali.yaml
Normal file
13
packages/operator/config/mwynar_w_dali.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_4064_mlynar_epoque#28
|
||||
logo: logo_kazimierz
|
||||
fallback_name: char_4064_mlynar_epoque#28
|
||||
viewport_left: 0
|
||||
viewport_right: 0
|
||||
viewport_top: 0
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 远路 · 玛恩纳
|
||||
en-US: W Dali / Młynar
|
||||
use_json: false
|
||||
official_id: '202310850'
|
||||
13
packages/operator/config/nearl.yaml
Normal file
13
packages/operator/config/nearl.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
filename: dyn_illust_char_1014_nearl2
|
||||
logo: logo_kazimierz
|
||||
fallback_name: char_1014_nearl2_2
|
||||
viewport_left: 2
|
||||
viewport_right: 3
|
||||
viewport_top: 10
|
||||
viewport_bottom: 0
|
||||
invert_filter: true
|
||||
codename:
|
||||
zh-CN: 耀骑士临光
|
||||
en-US: Nearl the Radiant Knight
|
||||
use_json: false
|
||||
official_id: '20220304'
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user