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(
+ `\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(
- `\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);
})();
}