diff --git a/src/cmds/authTest.ts b/src/cmds/authTest.ts index 02401c5..4e47698 100644 --- a/src/cmds/authTest.ts +++ b/src/cmds/authTest.ts @@ -265,7 +265,7 @@ async function mainCmdHandler() { ['Pool ID', 'Pool Name', 'Pulls', '*6', '*5', '*4', 'Pity *6', '*5', 'Latest'].map((e) => chalk.dim(e)), ...gachaPoolInfoList.map((e) => [ e.poolId, - e.pool_name, + e.pool_name ?? gachaRecordRsp.find((f) => f.poolId === e.poolId)!.poolName, ...[ gachaRecordRsp.filter((f) => f.poolId === e.poolId).length, gachaRecordRsp.filter((f) => f.poolId === e.poolId && f.rarity === 6).length, @@ -311,15 +311,17 @@ async function mainCmdHandler() { pityR6++; pityR5++; if (record.rarity >= 5) { - tableSubData.push([ - gachaPoolInfoEntry.poolId, - gachaPoolInfoEntry.pool_name, - DateTime.fromMillis(parseInt(record.gachaTs)).toFormat('yyyy/MM/dd hh:mm:ss'), - record.rarity === 6 ? pityR6 : pityR5, - record.rarity === 6 - ? chalk.yellow(`*${record.rarity} ${record.charName}`) - : chalk.magenta(`*${record.rarity} ${record.charName}`), - ]); + if (record.rarity === 6) { + tableSubData.push([ + gachaPoolInfoEntry.poolId, + gachaPoolInfoEntry.pool_name ?? records[0]!.poolName, + DateTime.fromMillis(parseInt(record.gachaTs)).toFormat('yyyy/MM/dd hh:mm:ss'), + record.rarity === 6 ? pityR6 : pityR5, + record.rarity === 6 + ? chalk.yellow(`*${record.rarity} ${record.charName}`) + : chalk.magenta(`*${record.rarity} ${record.charName}`), + ]); + } if (record.rarity === 6) pityR6 = 0; pityR5 = 0; } diff --git a/src/cmds/test.ts b/src/cmds/test.ts index 13d71c9..3d002cb 100644 --- a/src/cmds/test.ts +++ b/src/cmds/test.ts @@ -75,6 +75,428 @@ async function saveResult( } } +async function generateGameListMd(target: { name: string; dirName: string }) { + const outputDir = argvUtils.getArgv()['outputDir']; + const gameAllJsonPath = path.join(outputDir, 'akEndfield', 'launcher', 'game', target.dirName, 'all.json'); + + if (!(await Bun.file(gameAllJsonPath).exists())) return; + + const gameAllJson = await Bun.file(gameAllJsonPath).json(); + const mdTexts: string[] = []; + + const formatSize = (size: number) => + mathUtils.formatFileSize(size, { + decimals: 2, + decimalPadding: true, + unitVisible: true, + useBinaryUnit: true, + useBitUnit: false, + unit: null, + }); + + mdTexts.push(`# Game Packages (${target.name})\n`); + + // TOC + for (const e of gameAllJson) { + const version = e.rsp.version; + const date = DateTime.fromISO(e.updatedAt, { setZone: true }).setZone('UTC+8'); + const dateStr = date.toFormat('yyyy/MM/dd HH:mm:ss'); + const anchorId = `ver-${version}-${Math.ceil(date.toSeconds())}`; + mdTexts.push(`- [${version} (${dateStr})](#${anchorId})`); + } + mdTexts.push(''); + + // Content + for (const e of gameAllJson) { + const version = e.rsp.version; + const date = DateTime.fromISO(e.updatedAt, { setZone: true }).setZone('UTC+8'); + const dateStr = date.toFormat('yyyy/MM/dd HH:mm:ss'); + const anchorId = `ver-${version}-${Math.ceil(date.toSeconds())}`; + + const packedSize = mathUtils.arrayTotal(e.rsp.pkg.packs.map((f: any) => parseInt(f.package_size))); + const unpackedSize = parseInt(e.rsp.pkg.total_size) - packedSize; + + mdTexts.push( + `

${version} (${dateStr})

\n`, + ``, + ` `, + ` `, + `
Unpacked Size${formatSize(unpackedSize)}
Packed Size${formatSize(packedSize)}
\n`, + `|File|MD5 Checksum|Size|`, + `|:--|:--|--:|`, + ); + + for (const f of e.rsp.pkg.packs) { + const fileName = new URL(f.url).pathname.split('/').pop() ?? ''; + mdTexts.push(`|[${fileName}](${f.url})|\`${f.md5}\`|${formatSize(parseInt(f.package_size))}|`); + } + mdTexts.push(''); + } + + await Bun.write( + path.join(outputDir, 'akEndfield', 'launcher', 'game', target.dirName, 'list.md'), + mdTexts.join('\n'), + ); +} + +async function generatePatchListMd(target: { name: string; dirName: string }) { + const outputDir = argvUtils.getArgv()['outputDir']; + const patchAllJsonPath = path.join(outputDir, 'akEndfield', 'launcher', 'game', target.dirName, 'all_patch.json'); + + if (!(await Bun.file(patchAllJsonPath).exists())) return; + + const patchAllJson = await Bun.file(patchAllJsonPath).json(); + const mdTexts: string[] = []; + + const formatSize = (size: number) => + mathUtils.formatFileSize(size, { + decimals: 2, + decimalPadding: true, + unitVisible: true, + useBinaryUnit: true, + useBitUnit: false, + unit: null, + }); + + mdTexts.push(`# Game Patch Packages (${target.name})\n`); + + // TOC + for (const e of patchAllJson) { + const version = e.rsp.version; + const reqVersion = e.rsp.request_version; + const date = DateTime.fromISO(e.updatedAt, { setZone: true }).setZone('UTC+8'); + const dateStr = date.toFormat('yyyy/MM/dd HH:mm:ss'); + const anchorId = `ver-${reqVersion}-${version}-${Math.ceil(date.toSeconds())}`; + mdTexts.push(`- [${reqVersion} → ${version} (${dateStr})](#${anchorId})`); + } + mdTexts.push(''); + + // Content + for (const e of patchAllJson) { + const version = e.rsp.version; + const reqVersion = e.rsp.request_version; + const date = DateTime.fromISO(e.updatedAt, { setZone: true }).setZone('UTC+8'); + const dateStr = date.toFormat('yyyy/MM/dd HH:mm:ss'); + const anchorId = `ver-${reqVersion}-${version}-${Math.ceil(date.toSeconds())}`; + + const packedSize = mathUtils.arrayTotal(e.rsp.patch.patches.map((f: any) => parseInt(f.package_size))); + const unpackedSize = parseInt(e.rsp.patch.total_size) - packedSize; + + mdTexts.push( + `

${reqVersion} → ${version} (${dateStr})

\n`, + ``, + ` `, + ` `, + `
Unpacked Size${formatSize(unpackedSize)}
Packed Size${formatSize(packedSize)}
\n`, + `|File|MD5 Checksum|Size|`, + `|:--|:--|--:|`, + ); + + if (e.rsp.patch.url) { + const fileName = new URL(e.rsp.patch.url).pathname.split('/').pop() ?? ''; + mdTexts.push( + `|[${fileName}](${e.rsp.patch.url})|\`${e.rsp.patch.md5}\`|${formatSize(parseInt(e.rsp.patch.package_size))}|`, + ); + } + + for (const f of e.rsp.patch.patches) { + const fileName = new URL(f.url).pathname.split('/').pop() ?? ''; + mdTexts.push(`|[${fileName}](${f.url})|\`${f.md5}\`|${formatSize(parseInt(f.package_size))}|`); + } + mdTexts.push(''); + } + + await Bun.write( + path.join(outputDir, 'akEndfield', 'launcher', 'game', target.dirName, 'list_patch.md'), + mdTexts.join('\n'), + ); +} + +async function generateResourceListMd(channelStr: string) { + const outputDir = argvUtils.getArgv()['outputDir']; + const mdTexts: string[] = []; + mdTexts.push( + '# Game Resources\n', + '- [Windows](#res-Windows)', + '- [Android](#res-Android)', + '- [iOS](#res-iOS)', + '- [PlayStation](#res-PlayStation)\n', + ); + + const platforms = ['Windows', 'Android', 'iOS', 'PlayStation'] as const; + + for (const platform of platforms) { + const resourceAllJsonPath = path.join( + outputDir, + 'akEndfield', + 'launcher', + 'game_resources', + channelStr, + platform, + 'all.json', + ); + + if (!(await Bun.file(resourceAllJsonPath).exists())) continue; + + const gameAllJson = await Bun.file(resourceAllJsonPath).json(); + const resVersionSet: { + resVersion: string; + rsp: { rsp: Awaited> }; + versions: string[]; + }[] = (() => { + const resVersions: string[] = [...new Set(gameAllJson.map((e: any) => e.rsp.res_version))] as string[]; + const arr: { resVersion: string; rsp: any; versions: string[] }[] = []; + for (const resVersion of resVersions) { + arr.push({ + resVersion, + rsp: gameAllJson.find((e: any) => e.rsp.res_version === resVersion), + versions: [ + ...new Set(gameAllJson.filter((e: any) => e.rsp.res_version === resVersion).map((e: any) => e.req.version)), + ] as string[], + }); + } + return arr; + })(); + mdTexts.push( + `

${platform}

\n`, + '|Res version|Initial|Main|Game version|', + '|--|--|--|--|', + ...resVersionSet.map( + (resVerObj) => + `|\`${resVerObj.rsp.rsp.res_version}\`|[${resVerObj.rsp.rsp.resources.find((e) => e.name === 'initial')!.version}](${resVerObj.rsp.rsp.resources.find((e) => e.name === 'initial')!.path})|[${resVerObj.rsp.rsp.resources.find((e) => e.name === 'main')!.version}](${resVerObj.rsp.rsp.resources.find((e) => e.name === 'main')!.path})|${resVerObj.versions.sort((a, b) => semver.compare(b, a)).join(', ')}|`, + ), + '', + ); + } + + await Bun.write( + path.join(outputDir, 'akEndfield', 'launcher', 'game_resources', channelStr, 'list.md'), + mdTexts.join('\n'), + ); +} + +async function fetchAndSaveLatestGames(cfg: any, gameTargets: any[]) { + for (const target of gameTargets) { + logger.debug(`Fetching latestGame (${target.name}) ...`); + const rsp = await apiUtils.akEndfield.launcher.latestGame( + cfg.appCode.game.osWinRel, + target.launcherAppCode, + cfg.channel.osWinRel, + target.subChannel, + target.launcherSubChannel, + null, + ); + logger.info( + `Fetched latestGame (${target.name}): v${rsp.version}, ${mathUtils.formatFileSize( + parseInt(rsp.pkg.total_size) - mathUtils.arrayTotal(rsp.pkg.packs.map((e) => parseInt(e.package_size))), + { + decimals: 2, + decimalPadding: true, + useBinaryUnit: true, + useBitUnit: false, + unitVisible: true, + unit: null, + }, + )}`, + ); + const prettyRsp = { + req: { + appCode: cfg.appCode.game.osWinRel, + launcherAppCode: target.launcherAppCode, + channel: cfg.channel.osWinRel, + subChannel: target.subChannel, + launcherSubChannel: target.launcherSubChannel, + }, + rsp, + }; + + await saveResult(['akEndfield', 'launcher', 'game', target.dirName], rsp.version, prettyRsp); + } +} + +async function fetchAndSaveLatestGamePatches(cfg: any, gameTargets: any[]) { + for (const target of gameTargets) { + logger.debug(`Fetching latestGame (patch) (${target.name}) ...`); + const gameAllJsonPath = path.join( + argvUtils.getArgv()['outputDir'], + 'akEndfield', + 'launcher', + 'game', + target.dirName, + 'all.json', + ); + const patchAllJsonPath = path.join( + argvUtils.getArgv()['outputDir'], + 'akEndfield', + 'launcher', + 'game', + target.dirName, + 'all_patch.json', + ); + + if (!(await Bun.file(gameAllJsonPath).exists())) continue; + + const gameAllJson = await Bun.file(gameAllJsonPath).json(); + let patchAllJson: any[] = []; + if (await Bun.file(patchAllJsonPath).exists()) { + patchAllJson = await Bun.file(patchAllJsonPath).json(); + } + + const versionList = ([...new Set(gameAllJson.map((e: any) => e.rsp.version))] as string[]) + .sort((a, b) => semver.compare(b, a)) + .slice(1); + let needWrite: boolean = false; + const queue = new PQueue({ concurrency: appConfig.threadCount.network }); + for (const ver of versionList) { + queue.add(async () => { + const rsp = await apiUtils.akEndfield.launcher.latestGame( + cfg.appCode.game.osWinRel, + target.launcherAppCode, + cfg.channel.osWinRel, + target.subChannel, + target.launcherSubChannel, + ver, + ); + const prettyRsp = { + req: { + appCode: cfg.appCode.game.osWinRel, + launcherAppCode: target.launcherAppCode, + channel: cfg.channel.osWinRel, + subChannel: target.subChannel, + launcherSubChannel: target.launcherSubChannel, + version: ver, + }, + rsp, + }; + if (rsp.patch === null) return; + if ( + patchAllJson + .map((e: any) => JSON.stringify({ req: e.req, rsp: e.rsp })) + .includes(JSON.stringify(prettyRsp)) === false + ) { + logger.debug( + `Fetched latestGame (patch) (${target.name}): v${rsp.request_version} -> v${rsp.version}, ${mathUtils.formatFileSize( + parseInt(rsp.patch.total_size) - parseInt(rsp.patch.package_size), + { + decimals: 2, + decimalPadding: true, + unitVisible: true, + useBinaryUnit: true, + useBitUnit: false, + unit: null, + }, + )}`, + ); + patchAllJson.push({ + updatedAt: DateTime.now().toISO(), + ...prettyRsp, + }); + needWrite = true; + } + }); + } + await queue.onIdle(); + if (needWrite) { + await Bun.write(patchAllJsonPath, JSON.stringify(patchAllJson, null, 2)); + } + } +} + +async function fetchAndSaveLatestGameResources(cfg: any, channelStr: string) { + logger.debug('Fetching latestGameRes ...'); + + const platforms = ['Windows', 'Android', 'iOS', 'PlayStation'] as const; + + const gameAllJsonPath = path.join( + argvUtils.getArgv()['outputDir'], + 'akEndfield', + 'launcher', + 'game', + channelStr, + 'all.json', + ); + + if (!(await Bun.file(gameAllJsonPath).exists())) { + logger.warn('Skipping latestGameRes: game/all.json not found'); + return; + } + + const versionInfoList = ( + (await Bun.file(gameAllJsonPath).json()).map((e: any) => e.rsp) as Awaited< + ReturnType + >[] + ) + .map((e) => ({ + version: e.version, + versionMinor: semver.major(e.version) + '.' + semver.minor(e.version), + randStr: /_([^/]+)\/.+?$/.exec(e.pkg.file_path)![1], + })) + .sort((a, b) => semver.compare(b.version, a.version)); + + for (const platform of platforms) { + let isLatestWrote: boolean = false; + for (const versionInfoEntry of versionInfoList) { + if (!versionInfoEntry.randStr) throw new Error('version rand_str not found'); + const rsp = await apiUtils.akEndfield.launcher.latestGameResources( + cfg.appCode.game.osWinRel, + versionInfoEntry.versionMinor, + versionInfoEntry.version, + versionInfoEntry.randStr, + platform, + ); + logger.info(`Fetched latestGameRes: ${platform}, v${versionInfoEntry.version}, ${rsp.res_version}`); + const prettyRsp = { + req: { + appCode: cfg.appCode.game.osWinRel, + gameVersion: versionInfoEntry.versionMinor, + version: versionInfoEntry.version, + randStr: versionInfoEntry.randStr, + platform, + }, + rsp, + }; + + await saveResult( + ['akEndfield', 'launcher', 'game_resources', channelStr, platform], + versionInfoEntry.version, + prettyRsp, + !isLatestWrote, + ); + isLatestWrote = true; + } + } +} + +async function fetchAndSaveLatestLauncher(cfg: any, channelStr: string) { + logger.debug('Fetching latestLauncher ...'); + const launcherTargetAppList = ['EndField', 'official'] as const; + for (const launcherTargetAppEntry of launcherTargetAppList) { + const rsp = await apiUtils.akEndfield.launcher.latestLauncher( + cfg.appCode.launcher.osWinRel, + cfg.channel.osWinRel, + cfg.channel.osWinRel, + null, + launcherTargetAppEntry === 'official' ? null : launcherTargetAppEntry, + ); + logger.info(`Fetched latestLauncher: v${rsp.version}, ${launcherTargetAppEntry}`); + const prettyRsp = { + req: { + appCode: cfg.appCode.launcher.osWinRel, + channel: cfg.channel.osWinRel, + subChannel: cfg.channel.osWinRel, + targetApp: launcherTargetAppEntry === 'official' ? null : launcherTargetAppEntry, + }, + rsp, + }; + + await saveResult( + ['akEndfield', 'launcher', 'launcher', launcherTargetAppEntry, channelStr], + rsp.version, + prettyRsp, + ); + } +} + async function mainCmdHandler() { const cfg = appConfig.network.api.akEndfield; const channelStr = String(cfg.channel.osWinRel); @@ -103,413 +525,18 @@ async function mainCmdHandler() { }, ]; - for (const target of gameTargets) { - await (async () => { - logger.debug(`Fetching latestGame (${target.name}) ...`); - const rsp = await apiUtils.akEndfield.launcher.latestGame( - cfg.appCode.game.osWinRel, - target.launcherAppCode, - cfg.channel.osWinRel, - target.subChannel, - target.launcherSubChannel, - null, - ); - logger.info( - `Fetched latestGame (${target.name}): v${rsp.version}, ${mathUtils.formatFileSize( - parseInt(rsp.pkg.total_size) - mathUtils.arrayTotal(rsp.pkg.packs.map((e) => parseInt(e.package_size))), - { - decimals: 2, - decimalPadding: true, - useBinaryUnit: true, - useBitUnit: false, - unitVisible: true, - unit: null, - }, - )}`, - ); - const prettyRsp = { - req: { - appCode: cfg.appCode.game.osWinRel, - launcherAppCode: target.launcherAppCode, - channel: cfg.channel.osWinRel, - subChannel: target.subChannel, - launcherSubChannel: target.launcherSubChannel, - }, - rsp, - }; - - await saveResult(['akEndfield', 'launcher', 'game', target.dirName], rsp.version, prettyRsp); - })(); - } - - for (const target of gameTargets) { - await (async () => { - logger.debug(`Fetching latestGame (patch) (${target.name}) ...`); - const gameAllJsonPath = path.join( - argvUtils.getArgv()['outputDir'], - 'akEndfield', - 'launcher', - 'game', - target.dirName, - 'all.json', - ); - const patchAllJsonPath = path.join( - argvUtils.getArgv()['outputDir'], - 'akEndfield', - 'launcher', - 'game', - target.dirName, - 'all_patch.json', - ); - - if (!(await Bun.file(gameAllJsonPath).exists())) return; - - const gameAllJson = await Bun.file(gameAllJsonPath).json(); - let patchAllJson: any[] = []; - if (await Bun.file(patchAllJsonPath).exists()) { - patchAllJson = await Bun.file(patchAllJsonPath).json(); - } - - const versionList = ([...new Set(gameAllJson.map((e: any) => e.rsp.version))] as string[]) - .sort((a, b) => semver.compare(b, a)) - .slice(1); - let needWrite: boolean = false; - const queue = new PQueue({ concurrency: appConfig.threadCount.network }); - for (const ver of versionList) { - queue.add(async () => { - const rsp = await apiUtils.akEndfield.launcher.latestGame( - cfg.appCode.game.osWinRel, - target.launcherAppCode, - cfg.channel.osWinRel, - target.subChannel, - target.launcherSubChannel, - ver, - ); - const prettyRsp = { - req: { - appCode: cfg.appCode.game.osWinRel, - launcherAppCode: target.launcherAppCode, - channel: cfg.channel.osWinRel, - subChannel: target.subChannel, - launcherSubChannel: target.launcherSubChannel, - version: ver, - }, - rsp, - }; - if (rsp.patch === null) return; - if ( - patchAllJson - .map((e: any) => JSON.stringify({ req: e.req, rsp: e.rsp })) - .includes(JSON.stringify(prettyRsp)) === false - ) { - logger.debug( - `Fetched latestGame (patch) (${target.name}): v${rsp.request_version} -> v${rsp.version}, ${mathUtils.formatFileSize( - parseInt(rsp.patch.total_size) - parseInt(rsp.patch.package_size), - { - decimals: 2, - decimalPadding: true, - unitVisible: true, - useBinaryUnit: true, - useBitUnit: false, - unit: null, - }, - )}`, - ); - patchAllJson.push({ - updatedAt: DateTime.now().toISO(), - ...prettyRsp, - }); - needWrite = true; - } - }); - } - await queue.onIdle(); - if (needWrite) { - await Bun.write(patchAllJsonPath, JSON.stringify(patchAllJson, null, 2)); - } - })(); - } - - await (async () => { - logger.debug('Fetching latestGameRes ...'); - - const platforms = ['Windows', 'Android', 'iOS', 'PlayStation'] as const; - - const gameAllJsonPath = path.join( - argvUtils.getArgv()['outputDir'], - 'akEndfield', - 'launcher', - 'game', - channelStr, - 'all.json', - ); - - if (!(await Bun.file(gameAllJsonPath).exists())) { - logger.warn('Skipping latestGameRes: game/all.json not found'); - return; - } - - const versionInfoList = ( - (await Bun.file(gameAllJsonPath).json()).map((e: any) => e.rsp) as Awaited< - ReturnType - >[] - ) - .map((e) => ({ - version: e.version, - versionMinor: semver.major(e.version) + '.' + semver.minor(e.version), - randStr: /_([^/]+)\/.+?$/.exec(e.pkg.file_path)![1], - })) - .sort((a, b) => semver.compare(b.version, a.version)); - - for (const platform of platforms) { - let isLatestWrote: boolean = false; - for (const versionInfoEntry of versionInfoList) { - if (!versionInfoEntry.randStr) throw new Error('version rand_str not found'); - const rsp = await apiUtils.akEndfield.launcher.latestGameResources( - cfg.appCode.game.osWinRel, - versionInfoEntry.versionMinor, - versionInfoEntry.version, - versionInfoEntry.randStr, - platform, - ); - logger.info(`Fetched latestGameRes: ${platform}, v${versionInfoEntry.version}, ${rsp.res_version}`); - const prettyRsp = { - req: { - appCode: cfg.appCode.game.osWinRel, - gameVersion: versionInfoEntry.versionMinor, - version: versionInfoEntry.version, - randStr: versionInfoEntry.randStr, - platform, - }, - rsp, - }; - - await saveResult( - ['akEndfield', 'launcher', 'game_resources', channelStr, platform], - versionInfoEntry.version, - prettyRsp, - !isLatestWrote, - ); - isLatestWrote = true; - } - } - })(); - - await (async () => { - logger.debug('Fetching latestLauncher ...'); - const launcherTargetAppList = ['EndField', 'official'] as const; - for (const launcherTargetAppEntry of launcherTargetAppList) { - const rsp = await apiUtils.akEndfield.launcher.latestLauncher( - cfg.appCode.launcher.osWinRel, - cfg.channel.osWinRel, - cfg.channel.osWinRel, - null, - launcherTargetAppEntry === 'official' ? null : launcherTargetAppEntry, - ); - logger.info(`Fetched latestLauncher: v${rsp.version}, ${launcherTargetAppEntry}`); - const prettyRsp = { - req: { - appCode: cfg.appCode.launcher.osWinRel, - channel: cfg.channel.osWinRel, - subChannel: cfg.channel.osWinRel, - targetApp: launcherTargetAppEntry === 'official' ? null : launcherTargetAppEntry, - }, - rsp, - }; - - await saveResult( - ['akEndfield', 'launcher', 'launcher', launcherTargetAppEntry, channelStr], - rsp.version, - prettyRsp, - ); - } - })(); + await fetchAndSaveLatestGames(cfg, gameTargets); + await fetchAndSaveLatestGamePatches(cfg, gameTargets); + await fetchAndSaveLatestGameResources(cfg, channelStr); + await fetchAndSaveLatestLauncher(cfg, channelStr); await (async () => { //* Markdown generate for (const target of gameTargets) { - await (async () => { - const gameAllJsonPath = path.join( - argvUtils.getArgv()['outputDir'], - 'akEndfield', - 'launcher', - 'game', - target.dirName, - 'all.json', - ); - if (!(await Bun.file(gameAllJsonPath).exists())) return; - const gameAllJson = await Bun.file(gameAllJsonPath).json(); - const mdTexts: string[] = []; - mdTexts.push( - ...[ - `# Game Packages (${target.name})\n`, - ...gameAllJson.map( - (e: any) => - `- [${e.rsp.version} (${DateTime.fromISO(e.updatedAt, { setZone: true }).setZone('UTC+8').toFormat('yyyy/MM/dd HH:mm:ss')})](#ver-${e.rsp.version}-${Math.ceil(DateTime.fromISO(e.updatedAt).toSeconds())})`, - ), - '', - ], - ...gameAllJson.map((e: any) => - [ - `

${e.rsp.version} (${DateTime.fromISO(e.updatedAt, { setZone: true }).setZone('UTC+8').toFormat('yyyy/MM/dd HH:mm:ss')})

\n`, - ``, - ` `, - ` `, - `
Unpacked Size${mathUtils.formatFileSize( - e.rsp.pkg.total_size - mathUtils.arrayTotal(e.rsp.pkg.packs.map((f: any) => parseInt(f.package_size))), - { - decimals: 2, - decimalPadding: true, - unitVisible: true, - useBinaryUnit: true, - useBitUnit: false, - unit: null, - }, - )}
Packed Size${mathUtils.formatFileSize(mathUtils.arrayTotal(e.rsp.pkg.packs.map((f: any) => parseInt(f.package_size))), { decimals: 2, decimalPadding: true, unitVisible: true, useBinaryUnit: true, useBitUnit: false, unit: null })}
\n`, - `|File|MD5 Checksum|Size|`, - `|:--|:--|--:|`, - ...e.rsp.pkg.packs.map((f: any) => [ - `|[${new URL(f.url).pathname.split('/').pop() ?? ''}](${f.url})|\`${f.md5}\`|${mathUtils.formatFileSize(parseInt(f.package_size), { decimals: 2, decimalPadding: true, unitVisible: true, useBinaryUnit: true, useBitUnit: false, unit: null })}|`, - ]), - '', - ].join('\n'), - ), - ); - await Bun.write( - path.join(argvUtils.getArgv()['outputDir'], 'akEndfield', 'launcher', 'game', target.dirName, 'list.md'), - mdTexts.join('\n'), - ); - })(); - - await (async () => { - const patchAllJsonPath = path.join( - argvUtils.getArgv()['outputDir'], - 'akEndfield', - 'launcher', - 'game', - target.dirName, - 'all_patch.json', - ); - if (!(await Bun.file(patchAllJsonPath).exists())) return; - const gameAllJson = await Bun.file(patchAllJsonPath).json(); - const mdTexts: string[] = []; - mdTexts.push( - ...[ - `# Game Patch Packages (${target.name})\n`, - ...gameAllJson.map( - (e: any) => - `- [${e.rsp.request_version} → ${e.rsp.version} (${DateTime.fromISO(e.updatedAt, { setZone: true }).setZone('UTC+8').toFormat('yyyy/MM/dd HH:mm:ss')})](#ver-${e.rsp.request_version}-${e.rsp.version}-${Math.ceil(DateTime.fromISO(e.updatedAt).toSeconds())})`, - ), - '', - ], - ...gameAllJson.map((e: any) => - [ - `

${e.rsp.request_version} → ${e.rsp.version} (${DateTime.fromISO(e.updatedAt, { setZone: true }).setZone('UTC+8').toFormat('yyyy/MM/dd HH:mm:ss')})

\n`, - ``, - ` `, - ` `, - `
Unpacked Size${mathUtils.formatFileSize( - e.rsp.patch.total_size - - mathUtils.arrayTotal(e.rsp.patch.patches.map((f: any) => parseInt(f.package_size))), - { - decimals: 2, - decimalPadding: true, - unitVisible: true, - useBinaryUnit: true, - useBitUnit: false, - unit: null, - }, - )}
Packed Size${mathUtils.formatFileSize(mathUtils.arrayTotal(e.rsp.patch.patches.map((f: any) => parseInt(f.package_size))), { decimals: 2, decimalPadding: true, unitVisible: true, useBinaryUnit: true, useBitUnit: false, unit: null })}
\n`, - `|File|MD5 Checksum|Size|`, - `|:--|:--|--:|`, - ...(e.rsp.patch.url - ? [ - `|[${new URL(e.rsp.patch.url).pathname.split('/').pop() ?? ''}](${e.rsp.patch.url})|\`${e.rsp.patch.md5}\`|${mathUtils.formatFileSize(parseInt(e.rsp.patch.package_size), { decimals: 2, decimalPadding: true, unitVisible: true, useBinaryUnit: true, useBitUnit: false, unit: null })}|`, - ] - : []), - ...e.rsp.patch.patches.map((f: any) => [ - `|[${new URL(f.url).pathname.split('/').pop() ?? ''}](${f.url})|\`${f.md5}\`|${mathUtils.formatFileSize(parseInt(f.package_size), { decimals: 2, decimalPadding: true, unitVisible: true, useBinaryUnit: true, useBitUnit: false, unit: null })}|`, - ]), - '', - ].join('\n'), - ), - ); - await Bun.write( - path.join( - argvUtils.getArgv()['outputDir'], - 'akEndfield', - 'launcher', - 'game', - target.dirName, - 'list_patch.md', - ), - mdTexts.join('\n'), - ); - })(); + await generateGameListMd(target); + await generatePatchListMd(target); } - - await (async () => { - const mdTexts: string[] = []; - mdTexts.push( - '# Game Resources\n', - '- [Windows](#res-Windows)', - '- [Android](#res-Android)', - '- [iOS](#res-iOS)', - '- [PlayStation](#res-PlayStation)\n', - ); - // `

${e.rsp.version} (${DateTime.fromISO(e.updatedAt, { setZone: true }).setZone('UTC+8').toFormat('yyyy/MM/dd HH:mm:ss')})

\n` - - const platforms = ['Windows', 'Android', 'iOS', 'PlayStation'] as const; - - for (const platform of platforms) { - const gameAllJson = await Bun.file( - path.join( - argvUtils.getArgv()['outputDir'], - 'akEndfield', - 'launcher', - 'game_resources', - channelStr, - platform, - 'all.json', - ), - ).json(); - const resVersionSet: { - resVersion: string; - rsp: { rsp: Awaited> }; - versions: string[]; - }[] = (() => { - const resVersions: string[] = [...new Set(gameAllJson.map((e: any) => e.rsp.res_version))] as string[]; - const arr: { resVersion: string; rsp: any; versions: string[] }[] = []; - for (const resVersion of resVersions) { - arr.push({ - resVersion, - rsp: gameAllJson.find((e: any) => e.rsp.res_version === resVersion), - versions: [ - ...new Set( - gameAllJson.filter((e: any) => e.rsp.res_version === resVersion).map((e: any) => e.req.version), - ), - ] as string[], - }); - } - return arr; - })(); - mdTexts.push( - `

${platform}

\n`, - '|Res version|Initial|Main|Game version|', - '|--|--|--|--|', - ...resVersionSet.map( - (resVerObj) => - `|\`${resVerObj.rsp.rsp.res_version}\`|[${resVerObj.rsp.rsp.resources.find((e) => e.name === 'initial')!.version}](${resVerObj.rsp.rsp.resources.find((e) => e.name === 'initial')!.path})|[${resVerObj.rsp.rsp.resources.find((e) => e.name === 'main')!.version}](${resVerObj.rsp.rsp.resources.find((e) => e.name === 'main')!.path})|${resVerObj.versions.sort((a, b) => semver.compare(b, a)).join(', ')}|`, - ), - '', - ); - } - - await Bun.write( - path.join(argvUtils.getArgv()['outputDir'], 'akEndfield', 'launcher', 'game_resources', channelStr, 'list.md'), - mdTexts.join('\n'), - ); - })(); + await generateResourceListMd(channelStr); })(); }