mirror of
https://github.com/daydreamer-json/ak-endfield-api-archive.git
synced 2026-03-22 07:12:28 +01:00
feat: add epic games package archiving support
This commit is contained in:
456
src/cmds/test.ts
456
src/cmds/test.ts
@@ -79,112 +79,149 @@ async function mainCmdHandler() {
|
||||
const cfg = appConfig.network.api.akEndfield;
|
||||
const channelStr = String(cfg.channel.osWinRel);
|
||||
|
||||
await (async () => {
|
||||
logger.debug('Fetching latestGame ...');
|
||||
const rsp = await apiUtils.apiAkEndfield.launcher.latestGame(
|
||||
cfg.appCode.game.osWinRel,
|
||||
cfg.appCode.launcher.osWinRel,
|
||||
cfg.channel.osWinRel,
|
||||
cfg.channel.osWinRel,
|
||||
cfg.channel.osWinRel,
|
||||
null,
|
||||
);
|
||||
logger.info(
|
||||
`Fetched latestGame: 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: cfg.appCode.launcher.osWinRel,
|
||||
channel: cfg.channel.osWinRel,
|
||||
subChannel: cfg.channel.osWinRel,
|
||||
launcherSubChannel: cfg.channel.osWinRel,
|
||||
},
|
||||
rsp,
|
||||
};
|
||||
const gameTargets = [
|
||||
{
|
||||
name: 'Official',
|
||||
launcherAppCode: cfg.appCode.launcher.osWinRel,
|
||||
subChannel: cfg.channel.osWinRel,
|
||||
launcherSubChannel: cfg.channel.osWinRel,
|
||||
dirName: String(cfg.channel.osWinRel),
|
||||
},
|
||||
{
|
||||
name: 'Epic',
|
||||
launcherAppCode: cfg.appCode.launcher.osWinRelEpic,
|
||||
subChannel: cfg.subChannel.osWinRelEpic,
|
||||
launcherSubChannel: cfg.subChannel.osWinRelEpic,
|
||||
dirName: String(cfg.subChannel.osWinRelEpic),
|
||||
},
|
||||
];
|
||||
|
||||
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),
|
||||
for (const target of gameTargets) {
|
||||
await (async () => {
|
||||
logger.debug(`Fetching latestGame (${target.name}) ...`);
|
||||
const rsp = await apiUtils.apiAkEndfield.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.apiAkEndfield.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 ...');
|
||||
@@ -283,103 +320,126 @@ 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 })}|`,
|
||||
]),
|
||||
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())})`,
|
||||
),
|
||||
'',
|
||||
].join('\n'),
|
||||
),
|
||||
);
|
||||
await Bun.write(
|
||||
path.join(argvUtils.getArgv()['outputDir'], 'akEndfield', 'launcher', 'game', channelStr, 'list.md'),
|
||||
mdTexts.join('\n'),
|
||||
);
|
||||
})();
|
||||
],
|
||||
...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', target.dirName, '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 })}|`,
|
||||
]),
|
||||
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())})`,
|
||||
),
|
||||
'',
|
||||
].join('\n'),
|
||||
),
|
||||
);
|
||||
await Bun.write(
|
||||
path.join(argvUtils.getArgv()['outputDir'], 'akEndfield', 'launcher', 'game', channelStr, 'list_patch.md'),
|
||||
mdTexts.join('\n'),
|
||||
);
|
||||
})();
|
||||
],
|
||||
...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',
|
||||
target.dirName,
|
||||
'list_patch.md',
|
||||
),
|
||||
mdTexts.join('\n'),
|
||||
);
|
||||
})();
|
||||
}
|
||||
|
||||
await (async () => {
|
||||
const mdTexts: string[] = [];
|
||||
|
||||
464
src/utils/api.ts
464
src/utils/api.ts
@@ -1,461 +1,9 @@
|
||||
import ky from 'ky';
|
||||
import semver from 'semver';
|
||||
import * as TypesApiAkEndfield from '../types/api/akEndfield/Api.js';
|
||||
import appConfig from './config.js';
|
||||
|
||||
const defaultKySettings = {
|
||||
headers: {
|
||||
'User-Agent': appConfig.network.userAgent.minimum,
|
||||
},
|
||||
timeout: appConfig.network.timeout,
|
||||
retry: { limit: appConfig.network.retryCount },
|
||||
};
|
||||
|
||||
const launcherWebLang = [
|
||||
'de-de',
|
||||
'en-us',
|
||||
'es-mx',
|
||||
'fr-fr',
|
||||
'id-id',
|
||||
'it-it',
|
||||
'ja-jp',
|
||||
'ko-kr',
|
||||
'pt-br',
|
||||
'ru-ru',
|
||||
'th-th',
|
||||
'vi-vn',
|
||||
'zh-cn',
|
||||
'zh-tw',
|
||||
] as const;
|
||||
import accountService from './api/accountService.js';
|
||||
import binding from './api/binding.js';
|
||||
import launcher from './api/launcher.js';
|
||||
import u8 from './api/u8.js';
|
||||
import webview from './api/webview.js';
|
||||
|
||||
export default {
|
||||
defaultKySettings,
|
||||
apiAkEndfield: {
|
||||
launcher: {
|
||||
latestGame: async (
|
||||
appCode: string,
|
||||
launcherAppCode: string,
|
||||
channel: number,
|
||||
subChannel: number,
|
||||
launcherSubChannel: number,
|
||||
version: string | null,
|
||||
): Promise<TypesApiAkEndfield.LauncherLatestGame> => {
|
||||
if (version !== null && !semver.valid(version)) throw new Error(`Invalid version string (${version})`);
|
||||
const rsp = await ky
|
||||
.get(`https://${appConfig.network.api.akEndfield.base.launcher}/game/get_latest`, {
|
||||
...defaultKySettings,
|
||||
searchParams: {
|
||||
appcode: appCode,
|
||||
launcher_appcode: launcherAppCode,
|
||||
channel,
|
||||
sub_channel: subChannel,
|
||||
launcher_sub_channel: launcherSubChannel,
|
||||
version: version ?? undefined,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.LauncherLatestGame;
|
||||
},
|
||||
latestGameResources: async (
|
||||
appCode: string,
|
||||
gameVersion: string, // example: 1.0
|
||||
version: string,
|
||||
randStr: string,
|
||||
platform: 'Windows' | 'Android' | 'iOS' | 'PlayStation',
|
||||
): Promise<TypesApiAkEndfield.LauncherLatestGameResources> => {
|
||||
if (!semver.valid(version)) throw new Error(`Invalid version string (${version})`);
|
||||
const rsp = await ky
|
||||
.get(`https://${appConfig.network.api.akEndfield.base.launcher}/game/get_latest_resources`, {
|
||||
...defaultKySettings,
|
||||
searchParams: {
|
||||
appcode: appCode,
|
||||
game_version: gameVersion,
|
||||
version: version,
|
||||
platform,
|
||||
rand_str: randStr,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.LauncherLatestGameResources;
|
||||
},
|
||||
latestLauncher: async (
|
||||
appCode: string,
|
||||
channel: number,
|
||||
subChannel: number,
|
||||
version: string | null,
|
||||
targetApp: 'EndField' | null,
|
||||
): Promise<TypesApiAkEndfield.LauncherLatestLauncher> => {
|
||||
if (version !== null && !semver.valid(version)) throw new Error(`Invalid version string (${version})`);
|
||||
const rsp = await ky
|
||||
.get(`https://${appConfig.network.api.akEndfield.base.launcher}/launcher/get_latest`, {
|
||||
...defaultKySettings,
|
||||
searchParams: {
|
||||
appcode: appCode,
|
||||
channel,
|
||||
sub_channel: subChannel,
|
||||
version: version ?? undefined,
|
||||
targetApp: targetApp ?? undefined,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.LauncherLatestLauncher;
|
||||
},
|
||||
web: {
|
||||
sidebar: async (
|
||||
appCode: string,
|
||||
channel: number,
|
||||
subChannel: number,
|
||||
language: (typeof launcherWebLang)[number],
|
||||
platform: 'Windows' = 'Windows',
|
||||
): Promise<TypesApiAkEndfield.LauncherWebSidebar> => {
|
||||
const rsp = await ky
|
||||
.post(`https://${appConfig.network.api.akEndfield.base.launcher}/proxy/web/batch_proxy`, {
|
||||
...defaultKySettings,
|
||||
json: {
|
||||
proxy_reqs: [
|
||||
{
|
||||
kind: 'get_sidebar',
|
||||
get_sidebar_req: {
|
||||
appcode: appCode,
|
||||
channel: String(channel),
|
||||
sub_channel: String(subChannel),
|
||||
language,
|
||||
platform,
|
||||
source: 'launcher',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return (rsp as any).proxy_rsps[0].get_sidebar_rsp as TypesApiAkEndfield.LauncherWebSidebar;
|
||||
},
|
||||
singleEnt: async (
|
||||
appCode: string,
|
||||
channel: number,
|
||||
subChannel: number,
|
||||
language: (typeof launcherWebLang)[number],
|
||||
platform: 'Windows' = 'Windows',
|
||||
): Promise<TypesApiAkEndfield.LauncherWebSingleEnt> => {
|
||||
const rsp = await ky
|
||||
.post(`https://${appConfig.network.api.akEndfield.base.launcher}/proxy/web/batch_proxy`, {
|
||||
...defaultKySettings,
|
||||
json: {
|
||||
proxy_reqs: [
|
||||
{
|
||||
kind: 'get_single_ent',
|
||||
get_single_ent_req: {
|
||||
appcode: appCode,
|
||||
channel: String(channel),
|
||||
sub_channel: String(subChannel),
|
||||
language,
|
||||
platform,
|
||||
source: 'launcher',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return (rsp as any).proxy_rsps[0].get_single_ent_rsp as TypesApiAkEndfield.LauncherWebSingleEnt;
|
||||
},
|
||||
mainBgImage: async (
|
||||
appCode: string,
|
||||
channel: number,
|
||||
subChannel: number,
|
||||
language: (typeof launcherWebLang)[number],
|
||||
platform: 'Windows' = 'Windows',
|
||||
): Promise<TypesApiAkEndfield.LauncherWebMainBgImage> => {
|
||||
const rsp = await ky
|
||||
.post(`https://${appConfig.network.api.akEndfield.base.launcher}/proxy/web/batch_proxy`, {
|
||||
...defaultKySettings,
|
||||
json: {
|
||||
proxy_reqs: [
|
||||
{
|
||||
kind: 'get_main_bg_image',
|
||||
get_main_bg_image_req: {
|
||||
appcode: appCode,
|
||||
channel: String(channel),
|
||||
sub_channel: String(subChannel),
|
||||
language,
|
||||
platform,
|
||||
source: 'launcher',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return (rsp as any).proxy_rsps[0].get_main_bg_image_rsp as TypesApiAkEndfield.LauncherWebMainBgImage;
|
||||
},
|
||||
banner: async (
|
||||
appCode: string,
|
||||
channel: number,
|
||||
subChannel: number,
|
||||
language: (typeof launcherWebLang)[number],
|
||||
platform: 'Windows' = 'Windows',
|
||||
): Promise<TypesApiAkEndfield.LauncherWebBanner> => {
|
||||
const rsp = await ky
|
||||
.post(`https://${appConfig.network.api.akEndfield.base.launcher}/proxy/web/batch_proxy`, {
|
||||
...defaultKySettings,
|
||||
json: {
|
||||
proxy_reqs: [
|
||||
{
|
||||
kind: 'get_banner',
|
||||
get_banner_req: {
|
||||
appcode: appCode,
|
||||
channel: String(channel),
|
||||
sub_channel: String(subChannel),
|
||||
language,
|
||||
platform,
|
||||
source: 'launcher',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return (rsp as any).proxy_rsps[0].get_banner_rsp as TypesApiAkEndfield.LauncherWebBanner;
|
||||
},
|
||||
announcement: async (
|
||||
appCode: string,
|
||||
channel: number,
|
||||
subChannel: number,
|
||||
language: (typeof launcherWebLang)[number],
|
||||
platform: 'Windows' = 'Windows',
|
||||
): Promise<TypesApiAkEndfield.LauncherWebAnnouncement> => {
|
||||
const rsp = await ky
|
||||
.post(`https://${appConfig.network.api.akEndfield.base.launcher}/proxy/web/batch_proxy`, {
|
||||
...defaultKySettings,
|
||||
json: {
|
||||
proxy_reqs: [
|
||||
{
|
||||
kind: 'get_announcement',
|
||||
get_announcement_req: {
|
||||
appcode: appCode,
|
||||
channel: String(channel),
|
||||
sub_channel: String(subChannel),
|
||||
language,
|
||||
platform,
|
||||
source: 'launcher',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return (rsp as any).proxy_rsps[0].get_announcement_rsp as TypesApiAkEndfield.LauncherWebAnnouncement;
|
||||
},
|
||||
},
|
||||
},
|
||||
accountService: {
|
||||
user: {
|
||||
auth: {
|
||||
v1: {
|
||||
tokenByEmailPassword: async (
|
||||
email: string,
|
||||
password: string,
|
||||
from: number = 0,
|
||||
): Promise<TypesApiAkEndfield.AccSrvUserAuthV1TokenByEmail> => {
|
||||
const rsp = await ky
|
||||
.post(
|
||||
`https://${appConfig.network.api.akEndfield.base.accountService}/user/auth/v1/token_by_email_password`,
|
||||
{ ...defaultKySettings, json: { email, from, password } },
|
||||
)
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.AccSrvUserAuthV1TokenByEmail;
|
||||
},
|
||||
},
|
||||
},
|
||||
oauth2: {
|
||||
v2: {
|
||||
grant: async <T extends 0 | 1 = 0>(
|
||||
appCode: string,
|
||||
token: string,
|
||||
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 any;
|
||||
},
|
||||
},
|
||||
},
|
||||
info: {
|
||||
v1: {
|
||||
basic: async (appCode: string, token: string): Promise<TypesApiAkEndfield.AccSrvUserInfoV1Basic> => {
|
||||
const rsp = await ky
|
||||
.get(`https://${appConfig.network.api.akEndfield.base.accountService}/user/info/v1/basic`, {
|
||||
...defaultKySettings,
|
||||
searchParams: { appCode, token },
|
||||
})
|
||||
.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;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
u8: {
|
||||
user: {
|
||||
auth: {
|
||||
v2: {
|
||||
tokenByChToken: async (
|
||||
appCode: string,
|
||||
channelMasterId: number,
|
||||
channelToken: string,
|
||||
platform: number = 2,
|
||||
type: number = 0,
|
||||
): Promise<TypesApiAkEndfield.U8UserAuthV2ChToken> => {
|
||||
const rsp = await ky
|
||||
.post(`https://${appConfig.network.api.akEndfield.base.u8}/u8/user/auth/v2/token_by_channel_token`, {
|
||||
...defaultKySettings,
|
||||
json: {
|
||||
appCode,
|
||||
channelMasterId,
|
||||
channelToken: JSON.stringify({
|
||||
code: channelToken,
|
||||
type: 1,
|
||||
isSuc: true,
|
||||
}),
|
||||
type,
|
||||
platform,
|
||||
},
|
||||
})
|
||||
.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;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
game: {
|
||||
server: {
|
||||
v1: {
|
||||
serverList: async (token: string): Promise<TypesApiAkEndfield.U8GameServerV1ServerList> => {
|
||||
const rsp = await ky
|
||||
.post(`https://${appConfig.network.api.akEndfield.base.u8}/game/server/v1/server_list`, {
|
||||
...defaultKySettings,
|
||||
json: { token },
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.U8GameServerV1ServerList;
|
||||
},
|
||||
},
|
||||
},
|
||||
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;
|
||||
},
|
||||
},
|
||||
},
|
||||
apiAkEndfield: { launcher, accountService, u8, binding, webview },
|
||||
};
|
||||
|
||||
67
src/utils/api/accountService.ts
Normal file
67
src/utils/api/accountService.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import ky from 'ky';
|
||||
import * as TypesApiAkEndfield from '../../types/api/akEndfield/Api.js';
|
||||
import appConfig from '../config.js';
|
||||
import defaultSettings from './defaultSettings.js';
|
||||
|
||||
export default {
|
||||
user: {
|
||||
auth: {
|
||||
v1: {
|
||||
tokenByEmailPassword: async (
|
||||
email: string,
|
||||
password: string,
|
||||
from: number = 0,
|
||||
): Promise<TypesApiAkEndfield.AccSrvUserAuthV1TokenByEmail> => {
|
||||
const rsp = await ky
|
||||
.post(
|
||||
`https://${appConfig.network.api.akEndfield.base.accountService}/user/auth/v1/token_by_email_password`,
|
||||
{ ...defaultSettings.ky, json: { email, from, password } },
|
||||
)
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.AccSrvUserAuthV1TokenByEmail;
|
||||
},
|
||||
},
|
||||
},
|
||||
oauth2: {
|
||||
v2: {
|
||||
grant: async <T extends 0 | 1 = 0>(
|
||||
appCode: string,
|
||||
token: string,
|
||||
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`, {
|
||||
...defaultSettings.ky,
|
||||
json: { appCode, token, type },
|
||||
})
|
||||
.json();
|
||||
return rsp as any;
|
||||
},
|
||||
},
|
||||
},
|
||||
info: {
|
||||
v1: {
|
||||
basic: async (appCode: string, token: string): Promise<TypesApiAkEndfield.AccSrvUserInfoV1Basic> => {
|
||||
const rsp = await ky
|
||||
.get(`https://${appConfig.network.api.akEndfield.base.accountService}/user/info/v1/basic`, {
|
||||
...defaultSettings.ky,
|
||||
searchParams: { appCode, token },
|
||||
})
|
||||
.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`, {
|
||||
...defaultSettings.ky,
|
||||
searchParams: { appCode, token },
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.AccSrvUserInfoV1ThirdParty;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
34
src/utils/api/binding.ts
Normal file
34
src/utils/api/binding.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import ky from 'ky';
|
||||
import * as TypesApiAkEndfield from '../../types/api/akEndfield/Api.js';
|
||||
import appConfig from '../config.js';
|
||||
import defaultSettings from './defaultSettings.js';
|
||||
|
||||
export default {
|
||||
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`, {
|
||||
...defaultSettings.ky,
|
||||
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`, {
|
||||
...defaultSettings.ky,
|
||||
json: { token, uid },
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.BindApiAccBindV1U8TokenByUid;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
27
src/utils/api/defaultSettings.ts
Normal file
27
src/utils/api/defaultSettings.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import appConfig from '../config.js';
|
||||
|
||||
export default {
|
||||
ky: {
|
||||
headers: {
|
||||
'User-Agent': appConfig.network.userAgent.minimum,
|
||||
},
|
||||
timeout: appConfig.network.timeout,
|
||||
retry: { limit: appConfig.network.retryCount },
|
||||
},
|
||||
launcherWebLang: [
|
||||
'de-de',
|
||||
'en-us',
|
||||
'es-mx',
|
||||
'fr-fr',
|
||||
'id-id',
|
||||
'it-it',
|
||||
'ja-jp',
|
||||
'ko-kr',
|
||||
'pt-br',
|
||||
'ru-ru',
|
||||
'th-th',
|
||||
'vi-vn',
|
||||
'zh-cn',
|
||||
'zh-tw',
|
||||
] as const,
|
||||
};
|
||||
79
src/utils/api/launcher.ts
Normal file
79
src/utils/api/launcher.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import ky from 'ky';
|
||||
import semver from 'semver';
|
||||
import * as TypesApiAkEndfield from '../../types/api/akEndfield/Api.js';
|
||||
import appConfig from '../config.js';
|
||||
import defaultSettings from './defaultSettings.js';
|
||||
|
||||
import launcherWeb from './launcherWeb.js';
|
||||
|
||||
export default {
|
||||
latestGame: async (
|
||||
appCode: string,
|
||||
launcherAppCode: string,
|
||||
channel: number,
|
||||
subChannel: number,
|
||||
launcherSubChannel: number,
|
||||
version: string | null,
|
||||
): Promise<TypesApiAkEndfield.LauncherLatestGame> => {
|
||||
if (version !== null && !semver.valid(version)) throw new Error(`Invalid version string (${version})`);
|
||||
const rsp = await ky
|
||||
.get(`https://${appConfig.network.api.akEndfield.base.launcher}/game/get_latest`, {
|
||||
...defaultSettings.ky,
|
||||
searchParams: {
|
||||
appcode: appCode,
|
||||
launcher_appcode: launcherAppCode,
|
||||
channel,
|
||||
sub_channel: subChannel,
|
||||
launcher_sub_channel: launcherSubChannel,
|
||||
version: version ?? undefined,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.LauncherLatestGame;
|
||||
},
|
||||
latestGameResources: async (
|
||||
appCode: string,
|
||||
gameVersion: string, // example: 1.0
|
||||
version: string,
|
||||
randStr: string,
|
||||
platform: 'Windows' | 'Android' | 'iOS' | 'PlayStation',
|
||||
): Promise<TypesApiAkEndfield.LauncherLatestGameResources> => {
|
||||
if (!semver.valid(version)) throw new Error(`Invalid version string (${version})`);
|
||||
const rsp = await ky
|
||||
.get(`https://${appConfig.network.api.akEndfield.base.launcher}/game/get_latest_resources`, {
|
||||
...defaultSettings.ky,
|
||||
searchParams: {
|
||||
appcode: appCode,
|
||||
game_version: gameVersion,
|
||||
version: version,
|
||||
platform,
|
||||
rand_str: randStr,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.LauncherLatestGameResources;
|
||||
},
|
||||
latestLauncher: async (
|
||||
appCode: string,
|
||||
channel: number,
|
||||
subChannel: number,
|
||||
version: string | null,
|
||||
targetApp: 'EndField' | null,
|
||||
): Promise<TypesApiAkEndfield.LauncherLatestLauncher> => {
|
||||
if (version !== null && !semver.valid(version)) throw new Error(`Invalid version string (${version})`);
|
||||
const rsp = await ky
|
||||
.get(`https://${appConfig.network.api.akEndfield.base.launcher}/launcher/get_latest`, {
|
||||
...defaultSettings.ky,
|
||||
searchParams: {
|
||||
appcode: appCode,
|
||||
channel,
|
||||
sub_channel: subChannel,
|
||||
version: version ?? undefined,
|
||||
targetApp: targetApp ?? undefined,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.LauncherLatestLauncher;
|
||||
},
|
||||
web: launcherWeb,
|
||||
};
|
||||
152
src/utils/api/launcherWeb.ts
Normal file
152
src/utils/api/launcherWeb.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import ky from 'ky';
|
||||
import * as TypesApiAkEndfield from '../../types/api/akEndfield/Api.js';
|
||||
import appConfig from '../config.js';
|
||||
import defaultSettings from './defaultSettings.js';
|
||||
|
||||
export default {
|
||||
sidebar: async (
|
||||
appCode: string,
|
||||
channel: number,
|
||||
subChannel: number,
|
||||
language: (typeof defaultSettings.launcherWebLang)[number],
|
||||
platform: 'Windows' = 'Windows',
|
||||
): Promise<TypesApiAkEndfield.LauncherWebSidebar> => {
|
||||
const rsp = await ky
|
||||
.post(`https://${appConfig.network.api.akEndfield.base.launcher}/proxy/web/batch_proxy`, {
|
||||
...defaultSettings.ky,
|
||||
json: {
|
||||
proxy_reqs: [
|
||||
{
|
||||
kind: 'get_sidebar',
|
||||
get_sidebar_req: {
|
||||
appcode: appCode,
|
||||
channel: String(channel),
|
||||
sub_channel: String(subChannel),
|
||||
language,
|
||||
platform,
|
||||
source: 'launcher',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return (rsp as any).proxy_rsps[0].get_sidebar_rsp as TypesApiAkEndfield.LauncherWebSidebar;
|
||||
},
|
||||
singleEnt: async (
|
||||
appCode: string,
|
||||
channel: number,
|
||||
subChannel: number,
|
||||
language: (typeof defaultSettings.launcherWebLang)[number],
|
||||
platform: 'Windows' = 'Windows',
|
||||
): Promise<TypesApiAkEndfield.LauncherWebSingleEnt> => {
|
||||
const rsp = await ky
|
||||
.post(`https://${appConfig.network.api.akEndfield.base.launcher}/proxy/web/batch_proxy`, {
|
||||
...defaultSettings.ky,
|
||||
json: {
|
||||
proxy_reqs: [
|
||||
{
|
||||
kind: 'get_single_ent',
|
||||
get_single_ent_req: {
|
||||
appcode: appCode,
|
||||
channel: String(channel),
|
||||
sub_channel: String(subChannel),
|
||||
language,
|
||||
platform,
|
||||
source: 'launcher',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return (rsp as any).proxy_rsps[0].get_single_ent_rsp as TypesApiAkEndfield.LauncherWebSingleEnt;
|
||||
},
|
||||
mainBgImage: async (
|
||||
appCode: string,
|
||||
channel: number,
|
||||
subChannel: number,
|
||||
language: (typeof defaultSettings.launcherWebLang)[number],
|
||||
platform: 'Windows' = 'Windows',
|
||||
): Promise<TypesApiAkEndfield.LauncherWebMainBgImage> => {
|
||||
const rsp = await ky
|
||||
.post(`https://${appConfig.network.api.akEndfield.base.launcher}/proxy/web/batch_proxy`, {
|
||||
...defaultSettings.ky,
|
||||
json: {
|
||||
proxy_reqs: [
|
||||
{
|
||||
kind: 'get_main_bg_image',
|
||||
get_main_bg_image_req: {
|
||||
appcode: appCode,
|
||||
channel: String(channel),
|
||||
sub_channel: String(subChannel),
|
||||
language,
|
||||
platform,
|
||||
source: 'launcher',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return (rsp as any).proxy_rsps[0].get_main_bg_image_rsp as TypesApiAkEndfield.LauncherWebMainBgImage;
|
||||
},
|
||||
banner: async (
|
||||
appCode: string,
|
||||
channel: number,
|
||||
subChannel: number,
|
||||
language: (typeof defaultSettings.launcherWebLang)[number],
|
||||
platform: 'Windows' = 'Windows',
|
||||
): Promise<TypesApiAkEndfield.LauncherWebBanner> => {
|
||||
const rsp = await ky
|
||||
.post(`https://${appConfig.network.api.akEndfield.base.launcher}/proxy/web/batch_proxy`, {
|
||||
...defaultSettings.ky,
|
||||
json: {
|
||||
proxy_reqs: [
|
||||
{
|
||||
kind: 'get_banner',
|
||||
get_banner_req: {
|
||||
appcode: appCode,
|
||||
channel: String(channel),
|
||||
sub_channel: String(subChannel),
|
||||
language,
|
||||
platform,
|
||||
source: 'launcher',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return (rsp as any).proxy_rsps[0].get_banner_rsp as TypesApiAkEndfield.LauncherWebBanner;
|
||||
},
|
||||
announcement: async (
|
||||
appCode: string,
|
||||
channel: number,
|
||||
subChannel: number,
|
||||
language: (typeof defaultSettings.launcherWebLang)[number],
|
||||
platform: 'Windows' = 'Windows',
|
||||
): Promise<TypesApiAkEndfield.LauncherWebAnnouncement> => {
|
||||
const rsp = await ky
|
||||
.post(`https://${appConfig.network.api.akEndfield.base.launcher}/proxy/web/batch_proxy`, {
|
||||
...defaultSettings.ky,
|
||||
json: {
|
||||
proxy_reqs: [
|
||||
{
|
||||
kind: 'get_announcement',
|
||||
get_announcement_req: {
|
||||
appcode: appCode,
|
||||
channel: String(channel),
|
||||
sub_channel: String(subChannel),
|
||||
language,
|
||||
platform,
|
||||
source: 'launcher',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return (rsp as any).proxy_rsps[0].get_announcement_rsp as TypesApiAkEndfield.LauncherWebAnnouncement;
|
||||
},
|
||||
};
|
||||
82
src/utils/api/u8.ts
Normal file
82
src/utils/api/u8.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import ky from 'ky';
|
||||
import * as TypesApiAkEndfield from '../../types/api/akEndfield/Api.js';
|
||||
import appConfig from '../config.js';
|
||||
import defaultSettings from './defaultSettings.js';
|
||||
|
||||
export default {
|
||||
user: {
|
||||
auth: {
|
||||
v2: {
|
||||
tokenByChToken: async (
|
||||
appCode: string,
|
||||
channelMasterId: number,
|
||||
channelToken: string,
|
||||
platform: number = 2,
|
||||
type: number = 0,
|
||||
): Promise<TypesApiAkEndfield.U8UserAuthV2ChToken> => {
|
||||
const rsp = await ky
|
||||
.post(`https://${appConfig.network.api.akEndfield.base.u8}/u8/user/auth/v2/token_by_channel_token`, {
|
||||
...defaultSettings.ky,
|
||||
json: {
|
||||
appCode,
|
||||
channelMasterId,
|
||||
channelToken: JSON.stringify({
|
||||
code: channelToken,
|
||||
type: 1,
|
||||
isSuc: true,
|
||||
}),
|
||||
type,
|
||||
platform,
|
||||
},
|
||||
})
|
||||
.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`, {
|
||||
...defaultSettings.ky,
|
||||
json: { token, type, platform },
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.U8UserAuthV2Grant;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
game: {
|
||||
server: {
|
||||
v1: {
|
||||
serverList: async (token: string): Promise<TypesApiAkEndfield.U8GameServerV1ServerList> => {
|
||||
const rsp = await ky
|
||||
.post(`https://${appConfig.network.api.akEndfield.base.u8}/game/server/v1/server_list`, {
|
||||
...defaultSettings.ky,
|
||||
json: { token },
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.U8GameServerV1ServerList;
|
||||
},
|
||||
},
|
||||
},
|
||||
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`, {
|
||||
...defaultSettings.ky,
|
||||
json: { token, serverId: String(serverId) },
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.U8GameRoleV1ConfirmServer;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
40
src/utils/api/webview.ts
Normal file
40
src/utils/api/webview.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import ky from 'ky';
|
||||
import * as TypesApiAkEndfield from '../../types/api/akEndfield/Api.js';
|
||||
import appConfig from '../config.js';
|
||||
import defaultSettings from './defaultSettings.js';
|
||||
|
||||
export default {
|
||||
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 defaultSettings.launcherWebLang)[number] = 'ja-jp',
|
||||
): Promise<TypesApiAkEndfield.WebViewRecordChar> => {
|
||||
const rsp = await ky
|
||||
.get(`https://${appConfig.network.api.akEndfield.base.webview}/api/record/char`, {
|
||||
...defaultSettings.ky,
|
||||
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 defaultSettings.launcherWebLang)[number] = 'ja-jp',
|
||||
): Promise<TypesApiAkEndfield.WebViewRecordContent> => {
|
||||
const rsp = await ky
|
||||
.get(`https://${appConfig.network.api.akEndfield.base.webview}/api/content`, {
|
||||
...defaultSettings.ky,
|
||||
searchParams: { lang, pool_id: poolId, server_id: serverId },
|
||||
})
|
||||
.json();
|
||||
return rsp as TypesApiAkEndfield.WebViewRecordContent;
|
||||
},
|
||||
};
|
||||
@@ -17,11 +17,12 @@ type ConfigType = AllRequired<
|
||||
akEndfield: {
|
||||
appCode: {
|
||||
game: { osWinRel: string };
|
||||
launcher: { osWinRel: string };
|
||||
launcher: { osWinRel: string; osWinRelEpic: string };
|
||||
accountService: { osWinRel: string; skport: string; binding: string };
|
||||
u8: { osWinRel: string };
|
||||
};
|
||||
channel: { osWinRel: number };
|
||||
subChannel: { osWinRel: number; osWinRelEpic: number };
|
||||
base: {
|
||||
accountService: string;
|
||||
launcher: string;
|
||||
@@ -63,11 +64,12 @@ const initialConfig: ConfigType = {
|
||||
akEndfield: {
|
||||
appCode: {
|
||||
game: { osWinRel: 'YDUTE5gscDZ229CW' },
|
||||
launcher: { osWinRel: 'TiaytKBUIEdoEwRT' },
|
||||
launcher: { osWinRel: 'TiaytKBUIEdoEwRT', osWinRelEpic: 'BBWoqCzuZ2bZ1Dro' },
|
||||
accountService: { osWinRel: 'd9f6dbb6bbd6bb33', skport: '6eb76d4e13aa36e6', binding: '3dacefa138426cfe' },
|
||||
u8: { osWinRel: '973bd727dd11cbb6ead8' },
|
||||
},
|
||||
channel: { osWinRel: 6 },
|
||||
subChannel: { osWinRel: 6, osWinRelEpic: 801 },
|
||||
base: {
|
||||
accountService: 'YXMuZ3J5cGhsaW5lLmNvbQ==',
|
||||
launcher: 'bGF1bmNoZXIuZ3J5cGhsaW5lLmNvbS9hcGk=',
|
||||
|
||||
Reference in New Issue
Block a user