mirror of
https://github.com/daydreamer-json/ak-endfield-api-archive.git
synced 2026-02-04 06:15:04 +01:00
Add patch data
This commit is contained in:
@@ -44,7 +44,7 @@ async function parseCommand() {
|
||||
)
|
||||
.command(
|
||||
['authTest [token] [email] [password]'],
|
||||
'Auth test command',
|
||||
'Auth and gacha fetch test command',
|
||||
(yargs) => {
|
||||
yargs
|
||||
.positional('token', {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import chalk from 'chalk';
|
||||
import CliTable3 from 'cli-table3';
|
||||
import { HTTPError } from 'ky';
|
||||
import { DateTime } from 'luxon';
|
||||
import prompts from 'prompts';
|
||||
import apiUtils from '../utils/api.js';
|
||||
import argvUtils from '../utils/argv.js';
|
||||
@@ -35,8 +36,10 @@ async function mainCmdHandler() {
|
||||
argvUtils.setArgv({ ...argvUtils.getArgv(), token: tokenUserRsp });
|
||||
}
|
||||
}
|
||||
logger.info('Authorization in progress ...');
|
||||
if (needRetrieveToken === false) {
|
||||
try {
|
||||
logger.debug('Retrieving account service OAuth 2.0 code ...');
|
||||
oauth2TokenPreRsp = await apiUtils.apiAkEndfield.accountService.user.oauth2.v2.grant(
|
||||
cfg.appCode.accountService.osWinRel,
|
||||
argvUtils.getArgv()['token'],
|
||||
@@ -82,7 +85,7 @@ async function mainCmdHandler() {
|
||||
argvUtils.setArgv({ ...argvUtils.getArgv(), password: pwdRsp });
|
||||
}
|
||||
})();
|
||||
logger.info('Retrieving account service token ...');
|
||||
logger.debug('Retrieving account service token ...');
|
||||
const accSrvTokenRsp = await apiUtils.apiAkEndfield.accountService.user.auth.v1.tokenByEmailPassword(
|
||||
argvUtils.getArgv()['email'],
|
||||
argvUtils.getArgv()['password'],
|
||||
@@ -90,37 +93,116 @@ async function mainCmdHandler() {
|
||||
argvUtils.setArgv({ ...argvUtils.getArgv(), token: accSrvTokenRsp.data.token });
|
||||
}
|
||||
|
||||
logger.info('Retrieving account service OAuth 2.0 token ...');
|
||||
const oauth2TokenRsp = await apiUtils.apiAkEndfield.accountService.user.oauth2.v2.grant(
|
||||
cfg.appCode.accountService.osWinRel,
|
||||
oauth2TokenPreRsp === null ? logger.debug('Retrieving account service OAuth 2.0 code ...') : undefined;
|
||||
const oauth2TokenRsp =
|
||||
oauth2TokenPreRsp === null
|
||||
? await apiUtils.apiAkEndfield.accountService.user.oauth2.v2.grant(
|
||||
cfg.appCode.accountService.osWinRel,
|
||||
argvUtils.getArgv()['token'],
|
||||
)
|
||||
: oauth2TokenPreRsp;
|
||||
const oauth2TokenBindRsp = await apiUtils.apiAkEndfield.accountService.user.oauth2.v2.grant(
|
||||
cfg.appCode.accountService.binding,
|
||||
argvUtils.getArgv()['token'],
|
||||
1,
|
||||
);
|
||||
logger.info('Retrieving u8 access token ...');
|
||||
logger.debug('Retrieving u8 access token ...');
|
||||
const u8TokenRsp = await apiUtils.apiAkEndfield.u8.user.auth.v2.tokenByChToken(
|
||||
cfg.appCode.u8.osWinRel,
|
||||
cfg.channel.osWinRel,
|
||||
oauth2TokenRsp.data.code,
|
||||
);
|
||||
// logger.debug('Retrieving u8 OAuth 2.0 code ...');
|
||||
// const u8OAuth2Rsp = await apiUtils.apiAkEndfield.u8.user.auth.v2.grant(u8TokenRsp.data.token);
|
||||
logger.info('Authentication successful!');
|
||||
|
||||
logger.info('Retrieving user account data ...');
|
||||
logger.info('Retrieving user information data ...');
|
||||
|
||||
logger.debug('Retrieving user account data ...');
|
||||
const userAccData = await apiUtils.apiAkEndfield.accountService.user.info.v1.basic(
|
||||
cfg.appCode.accountService.osWinRel,
|
||||
argvUtils.getArgv()['token'],
|
||||
);
|
||||
logger.info('Retrieving user game server data ...');
|
||||
logger.debug('Retrieving user game server data ...');
|
||||
const userGameData = await apiUtils.apiAkEndfield.u8.game.server.v1.serverList(u8TokenRsp.data.token);
|
||||
const userGameBindingData = await apiUtils.apiAkEndfield.binding.account.binding.v1.bindingList(
|
||||
oauth2TokenBindRsp.data.token,
|
||||
);
|
||||
|
||||
logger.debug('Retrieving gacha record ...');
|
||||
const selectedServerId = await (async () => {
|
||||
const selectedServerAccData = userGameBindingData.data.list
|
||||
.find((f) => f.appCode === 'endfield')!
|
||||
.bindingList[0]!.roles.filter((e) => e.isBanned === false)
|
||||
.sort((a, b) => b.level - a.level)[0];
|
||||
if (!selectedServerAccData) throw new Error('Game account not found');
|
||||
const id = selectedServerAccData.serverId;
|
||||
logger.debug('Confirming server availability ...');
|
||||
const confirmServerRsp = await apiUtils.apiAkEndfield.u8.game.role.v1.confirmServer(
|
||||
u8TokenRsp.data.token,
|
||||
parseInt(id),
|
||||
);
|
||||
if (confirmServerRsp.status !== 0)
|
||||
throw new Error('Game server availability error: ' + JSON.stringify(confirmServerRsp));
|
||||
return id;
|
||||
})();
|
||||
const gachaRecordRsp = await (async () => {
|
||||
const overallRsp = await (async () => {
|
||||
const poolTypeList = [
|
||||
'E_CharacterGachaPoolType_Standard',
|
||||
'E_CharacterGachaPoolType_Beginner',
|
||||
'E_CharacterGachaPoolType_Special',
|
||||
] as const;
|
||||
const recordArr = [];
|
||||
for (const poolTypeEntry of poolTypeList) {
|
||||
let seqId: string | null = null;
|
||||
while (true) {
|
||||
const rsp = await apiUtils.apiAkEndfield.webview.record.char(
|
||||
u8TokenRsp.data.token,
|
||||
parseInt(selectedServerId),
|
||||
poolTypeEntry,
|
||||
seqId,
|
||||
);
|
||||
recordArr.push(...rsp.data.list.map((e) => ({ poolType: poolTypeEntry, ...e })));
|
||||
logger.trace(`Loaded: ${poolTypeEntry}, ${recordArr.length} entries, hasMore=${rsp.data.hasMore}`);
|
||||
if (rsp.data.hasMore === false) break;
|
||||
if (!rsp.data.list.at(-1)) break;
|
||||
seqId = rsp.data.list.at(-1)!.seqId;
|
||||
}
|
||||
}
|
||||
return recordArr;
|
||||
})();
|
||||
return overallRsp.toReversed();
|
||||
})();
|
||||
const gachaPoolInfoList = await (async () => {
|
||||
logger.debug('Retrieving gacha pool info ...');
|
||||
const arr = [];
|
||||
const poolIdList = [...new Set(gachaRecordRsp.map((e) => e.poolId))];
|
||||
for (const poolId of poolIdList) {
|
||||
const rsp = await apiUtils.apiAkEndfield.webview.content(parseInt(selectedServerId), poolId, 'ja-jp');
|
||||
arr.push({ poolId, ...rsp.data.pool });
|
||||
}
|
||||
return arr;
|
||||
})();
|
||||
logger.info('Data retrieval completed!');
|
||||
|
||||
(() => {
|
||||
const table = new CliTable3(termPrettyUtils.cliTableConfig.rounded);
|
||||
table.push(
|
||||
// [{ colSpan: 2, hAlign: 'center', content: chalk.bold('Account Info') }],
|
||||
...[
|
||||
['Account ID', userAccData.data.hgId],
|
||||
['Hypergryph ID', userAccData.data.hgId],
|
||||
['OAuth Grant ID', oauth2TokenRsp.data.uid],
|
||||
['Game Overall UID', userGameBindingData.data.list.find((e) => e.appCode === 'endfield')!.bindingList[0]!.uid],
|
||||
['Email', userAccData.data.realEmail],
|
||||
['Nickname', userAccData.data.nickName === '' ? chalk.dim('(none)') : userAccData.data.nickName],
|
||||
['Age Region', userAccData.data.ageGate.regionInfo['en-us']],
|
||||
[
|
||||
'Registered',
|
||||
DateTime.fromSeconds(
|
||||
userGameBindingData.data.list.find((e) => e.appCode === 'endfield')!.bindingList[0]!.registerTs,
|
||||
).toFormat('yyyy/MM/dd HH:mm:ss'),
|
||||
],
|
||||
].map((e) => [chalk.dim(e[0]), e[1]]),
|
||||
);
|
||||
console.log(table.toString());
|
||||
@@ -147,17 +229,108 @@ async function mainCmdHandler() {
|
||||
const table = new CliTable3(termPrettyUtils.cliTableConfig.rounded);
|
||||
table.push(
|
||||
...[
|
||||
['ID', 'UID', 'Level', 'Default'].map((e) => chalk.dim(e)),
|
||||
['ID', 'UID', 'Lv', 'Found', 'Default', 'Nickname', 'Registered'].map((e) => chalk.dim(e)),
|
||||
...userGameData.data.serverList.map((e) => [
|
||||
e.serverId,
|
||||
e.roleId,
|
||||
{ hAlign: 'right' as const, content: e.level },
|
||||
Boolean(
|
||||
userGameBindingData.data.list
|
||||
.find((f) => f.appCode === 'endfield')!
|
||||
.bindingList[0]!.roles.find((f) => f.serverId === e.serverId),
|
||||
),
|
||||
e.defaultChoose,
|
||||
userGameBindingData.data.list
|
||||
.find((f) => f.appCode === 'endfield')!
|
||||
.bindingList[0]!.roles.find((f) => f.serverId === e.serverId)?.nickName,
|
||||
userGameBindingData.data.list
|
||||
.find((f) => f.appCode === 'endfield')!
|
||||
.bindingList[0]!.roles.find((f) => f.serverId === e.serverId)?.registerTs
|
||||
? DateTime.fromSeconds(
|
||||
userGameBindingData.data.list
|
||||
.find((f) => f.appCode === 'endfield')!
|
||||
.bindingList[0]!.roles.find((f) => f.serverId === e.serverId)?.registerTs!,
|
||||
).toFormat('yyyy/MM/dd HH:mm:ss')
|
||||
: '',
|
||||
]),
|
||||
],
|
||||
);
|
||||
console.log(table.toString());
|
||||
})();
|
||||
|
||||
(() => {
|
||||
const table = new CliTable3(termPrettyUtils.cliTableConfig.rounded);
|
||||
table.push(
|
||||
...[
|
||||
['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,
|
||||
...[
|
||||
gachaRecordRsp.filter((f) => f.poolId === e.poolId).length,
|
||||
gachaRecordRsp.filter((f) => f.poolId === e.poolId && f.rarity === 6).length,
|
||||
gachaRecordRsp.filter((f) => f.poolId === e.poolId && f.rarity === 5).length,
|
||||
gachaRecordRsp.filter((f) => f.poolId === e.poolId && f.rarity === 4).length,
|
||||
(() => {
|
||||
let counter = 0;
|
||||
for (const pullEntry of gachaRecordRsp.filter((f) => f.poolId === e.poolId).toReversed()) {
|
||||
if (pullEntry.rarity >= 6) break;
|
||||
counter++;
|
||||
}
|
||||
return String(counter);
|
||||
})(),
|
||||
(() => {
|
||||
let counter = 0;
|
||||
for (const pullEntry of gachaRecordRsp.filter((f) => f.poolId === e.poolId).toReversed()) {
|
||||
if (pullEntry.rarity >= 5) break;
|
||||
counter++;
|
||||
}
|
||||
return String(counter);
|
||||
})(),
|
||||
].map((f) => ({ hAlign: 'right' as const, content: f })),
|
||||
(() => {
|
||||
const latestRecord = gachaRecordRsp.filter((f) => f.poolId === e.poolId).at(-1)!;
|
||||
const color = latestRecord.rarity === 6 ? chalk.yellow : chalk.magenta;
|
||||
return color(`*${latestRecord.rarity} ${latestRecord.charName}`);
|
||||
})(),
|
||||
]),
|
||||
],
|
||||
);
|
||||
console.log(table.toString());
|
||||
})();
|
||||
|
||||
(() => {
|
||||
const tableData: (string | number)[][] = [];
|
||||
tableData.push(['Pool ID', 'Pool Name', 'Pulled', 'Pity', 'Character'].map((e) => chalk.dim(e)));
|
||||
for (const [gachaPoolInfoIndex, gachaPoolInfoEntry] of Object.entries(gachaPoolInfoList)) {
|
||||
const records = gachaRecordRsp.filter((e) => e.poolId === gachaPoolInfoEntry.poolId);
|
||||
const tableSubData: typeof tableData = [];
|
||||
let pityR6: number = 0;
|
||||
let pityR5: number = 0;
|
||||
for (const record of records) {
|
||||
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) pityR6 = 0;
|
||||
pityR5 = 0;
|
||||
}
|
||||
}
|
||||
tableData.push(...tableSubData.toReversed());
|
||||
if (parseInt(gachaPoolInfoIndex) < gachaPoolInfoList.length - 1) tableData.push(Array(4).fill(''));
|
||||
}
|
||||
const table = new CliTable3(termPrettyUtils.cliTableConfig.rounded);
|
||||
table.push(...tableData);
|
||||
console.log(table.toString());
|
||||
})();
|
||||
}
|
||||
|
||||
export default mainCmdHandler;
|
||||
|
||||
212
src/cmds/test.ts
212
src/cmds/test.ts
@@ -1,5 +1,6 @@
|
||||
import path from 'node:path';
|
||||
import { DateTime } from 'luxon';
|
||||
import PQueue from 'p-queue';
|
||||
import semver from 'semver';
|
||||
import apiUtils from '../utils/api.js';
|
||||
import argvUtils from '../utils/argv.js';
|
||||
@@ -115,6 +116,76 @@ async function mainCmdHandler() {
|
||||
await saveResult(['akEndfield', 'launcher', 'game', channelStr], rsp.version, prettyRsp);
|
||||
})();
|
||||
|
||||
await (async () => {
|
||||
logger.debug('Fetching latestGame (patch) ...');
|
||||
const gameAllJson = await Bun.file(
|
||||
path.join(argvUtils.getArgv()['outputDir'], 'akEndfield', 'launcher', 'game', channelStr, 'all.json'),
|
||||
).json();
|
||||
const patchAllJson = await Bun.file(
|
||||
path.join(argvUtils.getArgv()['outputDir'], 'akEndfield', 'launcher', 'game', channelStr, 'all_patch.json'),
|
||||
).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.apiAkEndfield.launcher.latestGame(
|
||||
cfg.appCode.game.osWinRel,
|
||||
cfg.appCode.launcher.osWinRel,
|
||||
cfg.channel.osWinRel,
|
||||
cfg.channel.osWinRel,
|
||||
cfg.channel.osWinRel,
|
||||
ver,
|
||||
);
|
||||
const prettyRsp = {
|
||||
req: {
|
||||
appCode: cfg.appCode.game.osWinRel,
|
||||
launcherAppCode: cfg.appCode.launcher.osWinRel,
|
||||
channel: cfg.channel.osWinRel,
|
||||
subChannel: cfg.channel.osWinRel,
|
||||
launcherSubChannel: cfg.channel.osWinRel,
|
||||
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): 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(
|
||||
path.join(argvUtils.getArgv()['outputDir'], 'akEndfield', 'launcher', 'game', channelStr, 'all_patch.json'),
|
||||
JSON.stringify(patchAllJson, null, 2),
|
||||
);
|
||||
}
|
||||
})();
|
||||
|
||||
await (async () => {
|
||||
logger.debug('Fetching latestGameRes ...');
|
||||
|
||||
@@ -202,6 +273,147 @@ async function mainCmdHandler() {
|
||||
);
|
||||
}
|
||||
})();
|
||||
|
||||
await (async () => {
|
||||
//* Markdown generate
|
||||
await (async () => {
|
||||
const gameAllJson = await Bun.file(
|
||||
path.join(argvUtils.getArgv()['outputDir'], 'akEndfield', 'launcher', 'game', channelStr, 'all.json'),
|
||||
).json();
|
||||
const mdTexts: string[] = [];
|
||||
mdTexts.push(
|
||||
...[
|
||||
'# Game Packages\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) =>
|
||||
[
|
||||
`<h2 id="ver-${e.rsp.version}-${Math.ceil(DateTime.fromISO(e.updatedAt).toSeconds())}">${e.rsp.version} (${DateTime.fromISO(e.updatedAt, { setZone: true }).setZone('UTC+8').toFormat('yyyy/MM/dd HH:mm:ss')})</h2>\n`,
|
||||
`<table>`,
|
||||
` <tr><td>Unpacked Size</td><td style="text-align: right;"><b>${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,
|
||||
},
|
||||
)}</b></td></tr>`,
|
||||
` <tr><td>Packed Size</td><td style="text-align: right;"><b>${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 })}</b></td></tr>`,
|
||||
`</table>\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', channelStr, 'list.md'),
|
||||
mdTexts.join('\n'),
|
||||
);
|
||||
})();
|
||||
|
||||
await (async () => {
|
||||
const gameAllJson = await Bun.file(
|
||||
path.join(argvUtils.getArgv()['outputDir'], 'akEndfield', 'launcher', 'game', channelStr, 'all_patch.json'),
|
||||
).json();
|
||||
const mdTexts: string[] = [];
|
||||
mdTexts.push(
|
||||
...[
|
||||
'# Game Patch Packages\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) =>
|
||||
[
|
||||
`<h2 id="ver-${e.rsp.request_version}-${e.rsp.version}-${Math.ceil(DateTime.fromISO(e.updatedAt).toSeconds())}">${e.rsp.request_version} → ${e.rsp.version} (${DateTime.fromISO(e.updatedAt, { setZone: true }).setZone('UTC+8').toFormat('yyyy/MM/dd HH:mm:ss')})</h2>\n`,
|
||||
`<table>`,
|
||||
` <tr><td>Unpacked Size</td><td style="text-align: right;"><b>${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,
|
||||
},
|
||||
)}</b></td></tr>`,
|
||||
` <tr><td>Packed Size</td><td style="text-align: right;"><b>${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 })}</b></td></tr>`,
|
||||
`</table>\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', channelStr, 'list_patch.md'),
|
||||
mdTexts.join('\n'),
|
||||
);
|
||||
})();
|
||||
|
||||
await (async () => {
|
||||
const gameAllJson = await Bun.file(
|
||||
path.join(argvUtils.getArgv()['outputDir'], 'akEndfield', 'launcher', 'game_resources', channelStr, 'all.json'),
|
||||
).json();
|
||||
const resVersionSet: {
|
||||
resVersion: string;
|
||||
rsp: { rsp: Awaited<ReturnType<typeof apiUtils.apiAkEndfield.launcher.latestGameResources>> };
|
||||
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;
|
||||
})();
|
||||
const mdTexts: string[] = [];
|
||||
mdTexts.push(
|
||||
'# Game Resources\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.join(', ')}|`,
|
||||
),
|
||||
);
|
||||
await Bun.write(
|
||||
path.join(argvUtils.getArgv()['outputDir'], 'akEndfield', 'launcher', 'game_resources', channelStr, 'list.md'),
|
||||
mdTexts.join('\n'),
|
||||
);
|
||||
})();
|
||||
})();
|
||||
}
|
||||
|
||||
export default mainCmdHandler;
|
||||
|
||||
@@ -17,7 +17,18 @@ type LauncherLatestGame = {
|
||||
sub_channel: string;
|
||||
game_files_md5: string;
|
||||
};
|
||||
patch: unknown;
|
||||
patch: {
|
||||
url: string;
|
||||
md5: string;
|
||||
package_size: string;
|
||||
total_size: string;
|
||||
file_id: string;
|
||||
patches: {
|
||||
url: string;
|
||||
md5: string;
|
||||
package_size: string;
|
||||
}[];
|
||||
} | null;
|
||||
state: number;
|
||||
launcher_action: number;
|
||||
};
|
||||
@@ -156,11 +167,33 @@ type AccSrvUserInfoV1Basic = {
|
||||
type: string;
|
||||
};
|
||||
|
||||
type AccSrvUserInfoV1ThirdParty = {
|
||||
data: {
|
||||
thirdPartyInfo: {
|
||||
name: string;
|
||||
channelId: number; // 2=google, 3=facebook, 4=apple, 5=twitter
|
||||
}[];
|
||||
};
|
||||
msg: string;
|
||||
status: number;
|
||||
type: string;
|
||||
};
|
||||
|
||||
type AccSrvUserOAuth2V2Grant = {
|
||||
data: {
|
||||
uid: string; // ???
|
||||
uid: string; // grant uid (Gxxxxxxxxxx)
|
||||
code: string; // this is channel token
|
||||
};
|
||||
msg: string; // OK | Login status expired.
|
||||
status: number; // 0=OK, 3=expired
|
||||
type: string;
|
||||
};
|
||||
|
||||
type AccSrvUserOAuth2V2GrantType1 = {
|
||||
data: {
|
||||
hgId: string; // hypergryph account id
|
||||
token: string;
|
||||
};
|
||||
msg: string; // OK, Login status expired.
|
||||
status: number; // 0=OK, 3=expired
|
||||
type: string;
|
||||
@@ -177,6 +210,17 @@ type U8UserAuthV2ChToken = {
|
||||
type: string;
|
||||
};
|
||||
|
||||
type U8UserAuthV2Grant = {
|
||||
data: {
|
||||
uid: string; // game overall uid
|
||||
code: string;
|
||||
token: string;
|
||||
};
|
||||
msg: string;
|
||||
status: number;
|
||||
type: string;
|
||||
};
|
||||
|
||||
type U8GameServerV1ServerList = {
|
||||
data: {
|
||||
serverList: {
|
||||
@@ -194,6 +238,119 @@ type U8GameServerV1ServerList = {
|
||||
type: string;
|
||||
};
|
||||
|
||||
type U8GameRoleV1ConfirmServer = {
|
||||
msg: string;
|
||||
status: number;
|
||||
type: string;
|
||||
};
|
||||
|
||||
type BindApiAccBindV1BindList = {
|
||||
data: {
|
||||
list: {
|
||||
appCode: string; // endfield
|
||||
appName: string; // 明日方舟终末地
|
||||
supportMultiServer: boolean;
|
||||
bindingList: {
|
||||
uid: string; // game overall uid
|
||||
isOfficial: boolean; // maybe always true
|
||||
isDefault: boolean;
|
||||
channelMasterId: number; // 6
|
||||
channelName: string; // 官服
|
||||
isDeleted: boolean;
|
||||
isBanned: boolean;
|
||||
registerTs: number; // unix
|
||||
roles: {
|
||||
isBanned: boolean;
|
||||
serverId: string; // 2
|
||||
serverName: string; // Asia
|
||||
roleId: string; // game server uid
|
||||
nickName: string;
|
||||
level: number;
|
||||
isDefault: boolean;
|
||||
registerTs: number; // unix
|
||||
}[];
|
||||
}[];
|
||||
}[];
|
||||
};
|
||||
msg: string;
|
||||
status: number;
|
||||
};
|
||||
|
||||
type BindApiAccBindV1U8TokenByUid = {
|
||||
data: {
|
||||
token: string; // u8 token
|
||||
};
|
||||
msg: string;
|
||||
status: number;
|
||||
};
|
||||
|
||||
type BindApiGeneralV1AuthAppList = {
|
||||
data: {
|
||||
appList: {
|
||||
appCode: 'endfield' | 'arknights';
|
||||
appName: string;
|
||||
channel: {
|
||||
channelMasterId: number;
|
||||
channelName: string;
|
||||
isOfficial: boolean;
|
||||
}[];
|
||||
supportServer: boolean; // is support multi server loc
|
||||
serverList: {
|
||||
serverId: string; // 2 or 3 or something
|
||||
serverName: string; // Asia
|
||||
}[];
|
||||
}[];
|
||||
};
|
||||
msg: string;
|
||||
status: number;
|
||||
};
|
||||
|
||||
type WebViewRecordChar = {
|
||||
code: number; // 0 = ok
|
||||
data: {
|
||||
list: {
|
||||
poolId: string; // beginner, standard, special_1_0_1, ...
|
||||
poolName: string; // localized pool name
|
||||
charId: string; // chr_0016_laevat or something
|
||||
charName: string; // localized chara name
|
||||
rarity: number; // 6-4
|
||||
isFree: boolean;
|
||||
isNew: boolean;
|
||||
gachaTs: string; // unix
|
||||
seqId: string;
|
||||
}[];
|
||||
hasMore: boolean;
|
||||
};
|
||||
msg: string;
|
||||
};
|
||||
|
||||
type WebViewRecordContent = {
|
||||
code: number; // 0 = ok
|
||||
data: {
|
||||
pool: {
|
||||
pool_gacha_type: string; // char
|
||||
pool_name: string; // localized pool name
|
||||
pool_type: string; // newbie or special or normal
|
||||
up6_name: string; // localized up6 chara name, may blank
|
||||
up6_image: string;
|
||||
up5_name: string;
|
||||
up5_image: string;
|
||||
up6_item_name: string; // localized up6 item name (Laevatain's Token or something)
|
||||
rotate_image: string;
|
||||
ticket_name: string; // Firewalker's Trail HH Permit or something
|
||||
ticket_ten_name: string; // Firewalker's Trail 10×Permit or something
|
||||
all: {
|
||||
id: string; // chr_0016_laevat or something
|
||||
name: string; // localized chara name
|
||||
rarity: number; // 6-4
|
||||
}[];
|
||||
rotate_list: unknown[]; // ???
|
||||
};
|
||||
timezone: number; // asia is 8 (hour)
|
||||
};
|
||||
msg: string;
|
||||
};
|
||||
|
||||
export type {
|
||||
LauncherLatestGame,
|
||||
LauncherLatestGameResources,
|
||||
@@ -205,7 +362,16 @@ export type {
|
||||
LauncherWebAnnouncement,
|
||||
AccSrvUserAuthV1TokenByEmail,
|
||||
AccSrvUserInfoV1Basic,
|
||||
AccSrvUserInfoV1ThirdParty,
|
||||
AccSrvUserOAuth2V2Grant,
|
||||
AccSrvUserOAuth2V2GrantType1,
|
||||
U8UserAuthV2ChToken,
|
||||
U8UserAuthV2Grant,
|
||||
U8GameServerV1ServerList,
|
||||
U8GameRoleV1ConfirmServer,
|
||||
BindApiAccBindV1BindList,
|
||||
BindApiAccBindV1U8TokenByUid,
|
||||
BindApiGeneralV1AuthAppList,
|
||||
WebViewRecordChar,
|
||||
WebViewRecordContent,
|
||||
};
|
||||
|
||||
118
src/utils/api.ts
118
src/utils/api.ts
@@ -269,18 +269,20 @@ export default {
|
||||
},
|
||||
oauth2: {
|
||||
v2: {
|
||||
grant: async (
|
||||
grant: async <T extends 0 | 1 = 0>(
|
||||
appCode: string,
|
||||
token: string,
|
||||
type: number = 0,
|
||||
): Promise<TypesApiAkEndfield.AccSrvUserOAuth2V2Grant> => {
|
||||
type: T = 0 as any, // 0 = return grant uid (Gxxxxxxxxx) and code, 1 = return hgId and token
|
||||
): Promise<
|
||||
T extends 0 ? TypesApiAkEndfield.AccSrvUserOAuth2V2Grant : TypesApiAkEndfield.AccSrvUserOAuth2V2GrantType1
|
||||
> => {
|
||||
const rsp = await ky
|
||||
.post(`https://${appConfig.network.api.akEndfield.base.accountService}/user/oauth2/v2/grant`, {
|
||||
...defaultKySettings,
|
||||
json: { appCode, token, type },
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.AccSrvUserOAuth2V2Grant;
|
||||
return rsp as any;
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -295,6 +297,18 @@ export default {
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.AccSrvUserInfoV1Basic;
|
||||
},
|
||||
thirdParty: async (
|
||||
appCode: string,
|
||||
token: string,
|
||||
): Promise<TypesApiAkEndfield.AccSrvUserInfoV1ThirdParty> => {
|
||||
const rsp = await ky
|
||||
.get(`https://${appConfig.network.api.akEndfield.base.accountService}/user/info/v1/third_party`, {
|
||||
...defaultKySettings,
|
||||
searchParams: { appCode, token },
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.AccSrvUserInfoV1ThirdParty;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -328,6 +342,19 @@ export default {
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.U8UserAuthV2ChToken;
|
||||
},
|
||||
grant: async (
|
||||
token: string,
|
||||
platform: number = 2,
|
||||
type: number = 0,
|
||||
): Promise<TypesApiAkEndfield.U8UserAuthV2Grant> => {
|
||||
const rsp = await ky
|
||||
.post(`https://${appConfig.network.api.akEndfield.base.u8}/u8/user/auth/v2/grant`, {
|
||||
...defaultKySettings,
|
||||
json: { token, type, platform },
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.U8UserAuthV2Grant;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -345,6 +372,89 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
role: {
|
||||
v1: {
|
||||
confirmServer: async (
|
||||
token: string,
|
||||
serverId: number,
|
||||
): Promise<TypesApiAkEndfield.U8GameRoleV1ConfirmServer> => {
|
||||
const rsp = await ky
|
||||
.post(`https://${appConfig.network.api.akEndfield.base.u8}/game/role/v1/confirm_server`, {
|
||||
...defaultKySettings,
|
||||
json: { token, serverId: String(serverId) },
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.U8GameRoleV1ConfirmServer;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
binding: {
|
||||
account: {
|
||||
binding: {
|
||||
v1: {
|
||||
bindingList: async (
|
||||
// appCode: 'arknights' | 'endfield',
|
||||
token: string,
|
||||
): Promise<TypesApiAkEndfield.BindApiAccBindV1BindList> => {
|
||||
const rsp = await ky
|
||||
.get(`https://${appConfig.network.api.akEndfield.base.binding}/account/binding/v1/binding_list`, {
|
||||
...defaultKySettings,
|
||||
searchParams: { token },
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.BindApiAccBindV1BindList;
|
||||
},
|
||||
u8TokenByUid: async (
|
||||
token: string,
|
||||
uid: string,
|
||||
): Promise<TypesApiAkEndfield.BindApiAccBindV1U8TokenByUid> => {
|
||||
const rsp = await ky
|
||||
.post(`https://${appConfig.network.api.akEndfield.base.binding}/account/binding/v1/u8_token_by_uid`, {
|
||||
...defaultKySettings,
|
||||
json: { token, uid },
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.BindApiAccBindV1U8TokenByUid;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
webview: {
|
||||
record: {
|
||||
char: async (
|
||||
token: string,
|
||||
serverId: number, // 2 or 3
|
||||
poolType:
|
||||
| 'E_CharacterGachaPoolType_Beginner'
|
||||
| 'E_CharacterGachaPoolType_Standard'
|
||||
| 'E_CharacterGachaPoolType_Special',
|
||||
seqId: string | null,
|
||||
lang: (typeof launcherWebLang)[number] = 'ja-jp',
|
||||
): Promise<TypesApiAkEndfield.WebViewRecordChar> => {
|
||||
const rsp = await ky
|
||||
.get(`https://${appConfig.network.api.akEndfield.base.webview}/api/record/char`, {
|
||||
...defaultKySettings,
|
||||
searchParams: { lang, seq_id: seqId ?? undefined, pool_type: poolType, token, server_id: serverId },
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.WebViewRecordChar;
|
||||
},
|
||||
},
|
||||
content: async (
|
||||
serverId: number, // 2 or 3
|
||||
poolId: string,
|
||||
lang: (typeof launcherWebLang)[number] = 'ja-jp',
|
||||
): Promise<TypesApiAkEndfield.WebViewRecordContent> => {
|
||||
const rsp = await ky
|
||||
.get(`https://${appConfig.network.api.akEndfield.base.webview}/api/content`, {
|
||||
...defaultKySettings,
|
||||
searchParams: { lang, pool_id: poolId, server_id: serverId },
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.WebViewRecordContent;
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ type ConfigType = AllRequired<
|
||||
appCode: {
|
||||
game: { osWinRel: string };
|
||||
launcher: { osWinRel: string };
|
||||
accountService: { osWinRel: string };
|
||||
accountService: { osWinRel: string; skport: string; binding: string };
|
||||
u8: { osWinRel: string };
|
||||
};
|
||||
channel: { osWinRel: number };
|
||||
@@ -26,6 +26,8 @@ type ConfigType = AllRequired<
|
||||
accountService: string;
|
||||
launcher: string;
|
||||
u8: string;
|
||||
binding: string;
|
||||
webview: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -62,7 +64,7 @@ const initialConfig: ConfigType = {
|
||||
appCode: {
|
||||
game: { osWinRel: 'YDUTE5gscDZ229CW' },
|
||||
launcher: { osWinRel: 'TiaytKBUIEdoEwRT' },
|
||||
accountService: { osWinRel: 'd9f6dbb6bbd6bb33' },
|
||||
accountService: { osWinRel: 'd9f6dbb6bbd6bb33', skport: '6eb76d4e13aa36e6', binding: '3dacefa138426cfe' },
|
||||
u8: { osWinRel: '973bd727dd11cbb6ead8' },
|
||||
},
|
||||
channel: { osWinRel: 6 },
|
||||
@@ -70,6 +72,8 @@ const initialConfig: ConfigType = {
|
||||
accountService: 'YXMuZ3J5cGhsaW5lLmNvbQ==',
|
||||
launcher: 'bGF1bmNoZXIuZ3J5cGhsaW5lLmNvbS9hcGk=',
|
||||
u8: 'dTguZ3J5cGhsaW5lLmNvbQ==',
|
||||
binding: 'YmluZGluZy1hcGktYWNjb3VudC1wcm9kLmdyeXBobGluZS5jb20=',
|
||||
webview: 'ZWYtd2Vidmlldy5ncnlwaGxpbmUuY29t',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user