mirror of
https://github.com/daydreamer-json/ak-endfield-api-archive.git
synced 2026-02-04 06:15:04 +01:00
Add auth test
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
config/config.yaml
|
||||
memo/
|
||||
|
||||
# dependencies (bun install)
|
||||
node_modules
|
||||
|
||||
10
bun.lock
10
bun.lock
@@ -5,6 +5,7 @@
|
||||
"": {
|
||||
"name": "ak-endfield-api-archive",
|
||||
"dependencies": {
|
||||
"@types/prompts": "^2.4.9",
|
||||
"chalk": "^5.6.2",
|
||||
"cli-table3": "^0.6.5",
|
||||
"deepmerge": "^4.3.1",
|
||||
@@ -13,6 +14,7 @@
|
||||
"luxon": "^3.7.2",
|
||||
"ora": "^9.1.0",
|
||||
"p-queue": "^9.1.0",
|
||||
"prompts": "^2.4.2",
|
||||
"qs": "^6.14.1",
|
||||
"semver": "^7.7.3",
|
||||
"uuid": "^13.0.0",
|
||||
@@ -91,6 +93,8 @@
|
||||
|
||||
"@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="],
|
||||
|
||||
"@types/prompts": ["@types/prompts@2.4.9", "", { "dependencies": { "@types/node": "*", "kleur": "^3.0.3" } }, "sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA=="],
|
||||
|
||||
"@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="],
|
||||
|
||||
"@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="],
|
||||
@@ -203,6 +207,8 @@
|
||||
|
||||
"jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
|
||||
|
||||
"kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
|
||||
|
||||
"ky": ["ky@1.14.2", "", {}, "sha512-q3RBbsO5A5zrPhB6CaCS8ZUv+NWCXv6JJT4Em0i264G9W0fdPB8YRfnnEi7Dm7X7omAkBIPojzYJ2D1oHTHqug=="],
|
||||
|
||||
"log-symbols": ["log-symbols@7.0.1", "", { "dependencies": { "is-unicode-supported": "^2.0.0", "yoctocolors": "^2.1.1" } }, "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg=="],
|
||||
@@ -237,6 +243,8 @@
|
||||
|
||||
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
|
||||
|
||||
"pstree.remy": ["pstree.remy@1.1.8", "", {}, "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w=="],
|
||||
|
||||
"qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="],
|
||||
@@ -261,6 +269,8 @@
|
||||
|
||||
"simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="],
|
||||
|
||||
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
|
||||
|
||||
"stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="],
|
||||
|
||||
"streamroller": ["streamroller@3.1.5", "", { "dependencies": { "date-format": "^4.0.14", "debug": "^4.3.4", "fs-extra": "^8.1.0" } }, "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw=="],
|
||||
|
||||
@@ -2,15 +2,23 @@ network:
|
||||
api:
|
||||
akEndfield:
|
||||
appCode:
|
||||
game:
|
||||
osWinRel: YDUTE5gscDZ229CW
|
||||
launcherAppCode:
|
||||
launcher:
|
||||
osWinRel: TiaytKBUIEdoEwRT
|
||||
accountService:
|
||||
osWinRel: d9f6dbb6bbd6bb33
|
||||
u8:
|
||||
osWinRel: 973bd727dd11cbb6ead8
|
||||
osWinRel: YDUTE5gscDZ229CW
|
||||
channel:
|
||||
osWinRel: 6
|
||||
base:
|
||||
accountService: YXMuZ3J5cGhsaW5lLmNvbQ==
|
||||
launcher: bGF1bmNoZXIuZ3J5cGhsaW5lLmNvbS9hcGk=
|
||||
u8: dTguZ3J5cGhsaW5lLmNvbQ==
|
||||
launcherAppCode:
|
||||
osWinRel: TiaytKBUIEdoEwRT
|
||||
userAgent:
|
||||
minimum: Mozilla/5.0
|
||||
chromeWindows: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"start": "bun x biome format --write src && bun x oxfmt && bun src/main.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/prompts": "^2.4.9",
|
||||
"chalk": "^5.6.2",
|
||||
"cli-table3": "^0.6.5",
|
||||
"deepmerge": "^4.3.1",
|
||||
@@ -24,6 +25,7 @@
|
||||
"luxon": "^3.7.2",
|
||||
"ora": "^9.1.0",
|
||||
"p-queue": "^9.1.0",
|
||||
"prompts": "^2.4.2",
|
||||
"qs": "^6.14.1",
|
||||
"semver": "^7.7.3",
|
||||
"uuid": "^13.0.0",
|
||||
|
||||
21
src/cmd.ts
21
src/cmd.ts
@@ -42,6 +42,27 @@ async function parseCommand() {
|
||||
},
|
||||
wrapHandler(cmds.test),
|
||||
)
|
||||
.command(
|
||||
['authTest [token] [email] [password]'],
|
||||
'Auth test command',
|
||||
(yargs) => {
|
||||
yargs
|
||||
.positional('token', {
|
||||
describe: 'Gryphline account service token',
|
||||
type: 'string',
|
||||
})
|
||||
.positional('email', {
|
||||
describe: 'Gryphline account email address',
|
||||
type: 'string',
|
||||
})
|
||||
.positional('password', {
|
||||
describe: 'Gryphline account password',
|
||||
type: 'string',
|
||||
})
|
||||
.options({});
|
||||
},
|
||||
wrapHandler(cmds.authTest),
|
||||
)
|
||||
.options({
|
||||
'log-level': {
|
||||
desc: 'Set log level (' + TypesLogLevels.LOG_LEVELS_NUM.join(', ') + ')',
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import authTest from './cmds/authTest.js';
|
||||
import test from './cmds/test.js';
|
||||
|
||||
export default {
|
||||
authTest,
|
||||
test,
|
||||
};
|
||||
|
||||
163
src/cmds/authTest.ts
Normal file
163
src/cmds/authTest.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import chalk from 'chalk';
|
||||
import CliTable3 from 'cli-table3';
|
||||
import { HTTPError } from 'ky';
|
||||
import prompts from 'prompts';
|
||||
import apiUtils from '../utils/api.js';
|
||||
import argvUtils from '../utils/argv.js';
|
||||
import appConfig from '../utils/config.js';
|
||||
import exitUtils from '../utils/exit.js';
|
||||
import logger from '../utils/logger.js';
|
||||
import termPrettyUtils from '../utils/termPretty.js';
|
||||
|
||||
async function mainCmdHandler() {
|
||||
const cfg = appConfig.network.api.akEndfield;
|
||||
// const channelStr = String(cfg.channel.osWinRel);
|
||||
|
||||
let needRetrieveToken = false;
|
||||
let oauth2TokenPreRsp = null;
|
||||
if (!('token' in argvUtils.getArgv()) || !argvUtils.getArgv()['token']) {
|
||||
const tokenUserRsp: string = await (async () => {
|
||||
logger.warn('Gryphline account service token has not been specified. Requesting ...');
|
||||
const onCancelFn = () => {
|
||||
logger.error('Aborted');
|
||||
exitUtils.exit(1, null, false);
|
||||
};
|
||||
return (
|
||||
await prompts(
|
||||
{ name: 'value', type: 'password', message: 'Enter Gryphline account service token' },
|
||||
{ onCancel: onCancelFn },
|
||||
)
|
||||
).value;
|
||||
})();
|
||||
if (tokenUserRsp === '') {
|
||||
needRetrieveToken = true;
|
||||
} else {
|
||||
argvUtils.setArgv({ ...argvUtils.getArgv(), token: tokenUserRsp });
|
||||
}
|
||||
}
|
||||
if (needRetrieveToken === false) {
|
||||
try {
|
||||
oauth2TokenPreRsp = await apiUtils.apiAkEndfield.accountService.user.oauth2.v2.grant(
|
||||
cfg.appCode.accountService.osWinRel,
|
||||
argvUtils.getArgv()['token'],
|
||||
);
|
||||
} catch (err) {
|
||||
if (err instanceof HTTPError) {
|
||||
if ((await err.response.json()).status === 3) needRetrieveToken = true;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (needRetrieveToken) {
|
||||
await (async () => {
|
||||
const onCancelFn = () => {
|
||||
logger.error('Aborted');
|
||||
exitUtils.exit(1, null, false);
|
||||
};
|
||||
if (!('email' in argvUtils.getArgv())) {
|
||||
logger.warn('Gryphline account email has not been specified. Requesting ...');
|
||||
const emailRsp: number = (
|
||||
await prompts(
|
||||
{
|
||||
...{ name: 'value', type: 'text', message: 'Enter Gryphline account email' },
|
||||
validate: (value) => (Boolean(value) ? true : 'Invalid value'),
|
||||
},
|
||||
{ onCancel: onCancelFn },
|
||||
)
|
||||
).value;
|
||||
argvUtils.setArgv({ ...argvUtils.getArgv(), email: emailRsp });
|
||||
}
|
||||
if (!('password' in argvUtils.getArgv())) {
|
||||
// logger.warn('Gryphline account password has not been specified. Requesting ...');
|
||||
const pwdRsp: number = (
|
||||
await prompts(
|
||||
{
|
||||
...{ name: 'value', type: 'password', message: 'Enter Gryphline account password' },
|
||||
validate: (value) => (Boolean(value) ? true : 'Invalid value'),
|
||||
},
|
||||
{ onCancel: onCancelFn },
|
||||
)
|
||||
).value;
|
||||
argvUtils.setArgv({ ...argvUtils.getArgv(), password: pwdRsp });
|
||||
}
|
||||
})();
|
||||
logger.info('Retrieving account service token ...');
|
||||
const accSrvTokenRsp = await apiUtils.apiAkEndfield.accountService.user.auth.v1.tokenByEmailPassword(
|
||||
argvUtils.getArgv()['email'],
|
||||
argvUtils.getArgv()['password'],
|
||||
);
|
||||
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,
|
||||
argvUtils.getArgv()['token'],
|
||||
);
|
||||
logger.info('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.info('Authentication successful!');
|
||||
|
||||
logger.info('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 ...');
|
||||
const userGameData = await apiUtils.apiAkEndfield.u8.game.server.v1.serverList(u8TokenRsp.data.token);
|
||||
|
||||
logger.info('Data retrieval completed!');
|
||||
|
||||
(() => {
|
||||
const table = new CliTable3(termPrettyUtils.cliTableConfig.rounded);
|
||||
table.push(
|
||||
...[
|
||||
['Account ID', userAccData.data.hgId],
|
||||
['Email', userAccData.data.realEmail],
|
||||
['Nickname', userAccData.data.nickName === '' ? chalk.dim('(none)') : userAccData.data.nickName],
|
||||
['Age Region', userAccData.data.ageGate.regionInfo['en-us']],
|
||||
].map((e) => [chalk.dim(e[0]), e[1]]),
|
||||
);
|
||||
console.log(table.toString());
|
||||
})();
|
||||
|
||||
(() => {
|
||||
const table = new CliTable3(termPrettyUtils.cliTableConfig.rounded);
|
||||
table.push(
|
||||
...[
|
||||
['ID', 'Time', 'Name', 'Domain', 'Port'].map((e) => chalk.dim(e)),
|
||||
...userGameData.data.serverList.map((e) => [
|
||||
e.serverId,
|
||||
'UTC' + (JSON.parse(e.extension).offsetSeconds < 0 ? '' : '+') + JSON.parse(e.extension).offsetSeconds / 3600,
|
||||
e.serverName,
|
||||
JSON.parse(e.serverDomain)[0].host,
|
||||
JSON.parse(e.serverDomain)[0].port,
|
||||
]),
|
||||
],
|
||||
);
|
||||
console.log(table.toString());
|
||||
})();
|
||||
|
||||
(() => {
|
||||
const table = new CliTable3(termPrettyUtils.cliTableConfig.rounded);
|
||||
table.push(
|
||||
...[
|
||||
['ID', 'UID', 'Level', 'Default'].map((e) => chalk.dim(e)),
|
||||
...userGameData.data.serverList.map((e) => [
|
||||
e.serverId,
|
||||
e.roleId,
|
||||
{ hAlign: 'right' as const, content: e.level },
|
||||
e.defaultChoose,
|
||||
]),
|
||||
],
|
||||
);
|
||||
console.log(table.toString());
|
||||
})();
|
||||
}
|
||||
|
||||
export default mainCmdHandler;
|
||||
@@ -81,8 +81,8 @@ async function mainCmdHandler() {
|
||||
await (async () => {
|
||||
logger.debug('Fetching latestGame ...');
|
||||
const rsp = await apiUtils.apiAkEndfield.launcher.latestGame(
|
||||
cfg.appCode.osWinRel,
|
||||
cfg.launcherAppCode.osWinRel,
|
||||
cfg.appCode.game.osWinRel,
|
||||
cfg.appCode.launcher.osWinRel,
|
||||
cfg.channel.osWinRel,
|
||||
cfg.channel.osWinRel,
|
||||
cfg.channel.osWinRel,
|
||||
@@ -103,8 +103,8 @@ async function mainCmdHandler() {
|
||||
);
|
||||
const prettyRsp = {
|
||||
req: {
|
||||
appCode: cfg.appCode.osWinRel,
|
||||
launcherAppCode: cfg.launcherAppCode.osWinRel,
|
||||
appCode: cfg.appCode.game.osWinRel,
|
||||
launcherAppCode: cfg.appCode.launcher.osWinRel,
|
||||
channel: cfg.channel.osWinRel,
|
||||
subChannel: cfg.channel.osWinRel,
|
||||
launcherSubChannel: cfg.channel.osWinRel,
|
||||
@@ -147,7 +147,7 @@ async function mainCmdHandler() {
|
||||
for (const versionInfoEntry of versionInfoList) {
|
||||
if (!versionInfoEntry.randStr) throw new Error('version rand_str not found');
|
||||
const rsp = await apiUtils.apiAkEndfield.launcher.latestGameResources(
|
||||
cfg.appCode.osWinRel,
|
||||
cfg.appCode.game.osWinRel,
|
||||
versionInfoEntry.versionMinor,
|
||||
versionInfoEntry.version,
|
||||
versionInfoEntry.randStr,
|
||||
@@ -155,7 +155,7 @@ async function mainCmdHandler() {
|
||||
logger.info(`Fetched latestGameRes: v${versionInfoEntry.version}, ${rsp.res_version}`);
|
||||
const prettyRsp = {
|
||||
req: {
|
||||
appCode: cfg.appCode.osWinRel,
|
||||
appCode: cfg.appCode.game.osWinRel,
|
||||
gameVersion: versionInfoEntry.versionMinor,
|
||||
version: versionInfoEntry.version,
|
||||
randStr: versionInfoEntry.randStr,
|
||||
@@ -178,7 +178,7 @@ async function mainCmdHandler() {
|
||||
const launcherTargetAppList = ['EndField', 'official'] as const;
|
||||
for (const launcherTargetAppEntry of launcherTargetAppList) {
|
||||
const rsp = await apiUtils.apiAkEndfield.launcher.latestLauncher(
|
||||
cfg.launcherAppCode.osWinRel,
|
||||
cfg.appCode.launcher.osWinRel,
|
||||
cfg.channel.osWinRel,
|
||||
cfg.channel.osWinRel,
|
||||
null,
|
||||
@@ -187,7 +187,7 @@ async function mainCmdHandler() {
|
||||
logger.info(`Fetched latestLauncher: v${rsp.version}, ${launcherTargetAppEntry}`);
|
||||
const prettyRsp = {
|
||||
req: {
|
||||
appCode: cfg.launcherAppCode.osWinRel,
|
||||
appCode: cfg.appCode.launcher.osWinRel,
|
||||
channel: cfg.channel.osWinRel,
|
||||
subChannel: cfg.channel.osWinRel,
|
||||
targetApp: launcherTargetAppEntry === 'official' ? null : launcherTargetAppEntry,
|
||||
|
||||
@@ -106,6 +106,94 @@ type LauncherWebAnnouncement = {
|
||||
}[];
|
||||
};
|
||||
|
||||
type AccSrvUserAuthV1TokenByEmail = {
|
||||
data: {
|
||||
token: string;
|
||||
hgId: string; // hypergryph account id
|
||||
email: string; // obfuscated email
|
||||
isLatestUserAgreement: boolean;
|
||||
};
|
||||
msg: string;
|
||||
status: number;
|
||||
type: string;
|
||||
};
|
||||
|
||||
type AccSrvUserInfoV1Basic = {
|
||||
data: {
|
||||
hgId: string; // hypergryph account id
|
||||
email: string; // obfuscated email
|
||||
realEmail: string; // un-obfuscated email
|
||||
isLatestUserAgreement: boolean;
|
||||
nickName: string;
|
||||
emailSubscription: boolean; // ???
|
||||
extension: { firebaseHashedInfo: string };
|
||||
ageGate: {
|
||||
ageAuthState: number;
|
||||
bindEmail: boolean;
|
||||
parentAuthState: number;
|
||||
regionInfo: Record<
|
||||
| 'de-de'
|
||||
| 'en-us' // Japan
|
||||
| 'es-mx'
|
||||
| 'fr-fr'
|
||||
| 'id-id'
|
||||
| 'it-it'
|
||||
| 'ja-jp' // 日本
|
||||
| 'ko-kr'
|
||||
| 'pt-br'
|
||||
| 'ru-ru'
|
||||
| 'th-th'
|
||||
| 'vi-vn'
|
||||
| 'zh-cn' // 日本
|
||||
| 'zh-tw',
|
||||
string
|
||||
>;
|
||||
regionCode: string; // JP
|
||||
};
|
||||
};
|
||||
msg: string;
|
||||
status: number;
|
||||
type: string;
|
||||
};
|
||||
|
||||
type AccSrvUserOAuth2V2Grant = {
|
||||
data: {
|
||||
uid: string; // ???
|
||||
code: string; // this is channel token
|
||||
};
|
||||
msg: string; // OK, Login status expired.
|
||||
status: number; // 0=OK, 3=expired
|
||||
type: string;
|
||||
};
|
||||
|
||||
type U8UserAuthV2ChToken = {
|
||||
data: {
|
||||
token: string;
|
||||
isNew: boolean;
|
||||
uid: string; // number, game overall uid?
|
||||
};
|
||||
msg: string;
|
||||
status: number;
|
||||
type: string;
|
||||
};
|
||||
|
||||
type U8GameServerV1ServerList = {
|
||||
data: {
|
||||
serverList: {
|
||||
serverId: string; // number
|
||||
serverName: string; // Asia
|
||||
serverDomain: string; // jsonStr [{"host": "beyond-asiapacific.gryphline.com", "port": 30000}]
|
||||
defaultChoose: boolean;
|
||||
roleId: string; // the so-called UID elsewhere
|
||||
level: number; // playerLv
|
||||
extension: string; // jsonStr {"offsetSeconds": -18000, "monthlyCardOffsetSecond": -18000}
|
||||
}[];
|
||||
};
|
||||
msg: string;
|
||||
status: number;
|
||||
type: string;
|
||||
};
|
||||
|
||||
export type {
|
||||
LauncherLatestGame,
|
||||
LauncherLatestGameResources,
|
||||
@@ -115,4 +203,9 @@ export type {
|
||||
LauncherWebMainBgImage,
|
||||
LauncherWebBanner,
|
||||
LauncherWebAnnouncement,
|
||||
AccSrvUserAuthV1TokenByEmail,
|
||||
AccSrvUserInfoV1Basic,
|
||||
AccSrvUserOAuth2V2Grant,
|
||||
U8UserAuthV2ChToken,
|
||||
U8GameServerV1ServerList,
|
||||
};
|
||||
|
||||
101
src/utils/api.ts
101
src/utils/api.ts
@@ -1,6 +1,6 @@
|
||||
import ky from 'ky';
|
||||
import semver from 'semver';
|
||||
import * as TypesApiAkEndfield from '../types/api/akEndfield/api.js';
|
||||
import * as TypesApiAkEndfield from '../types/api/akEndfield/Api.js';
|
||||
import appConfig from './config.js';
|
||||
|
||||
const defaultKySettings = {
|
||||
@@ -248,5 +248,104 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
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 (
|
||||
appCode: string,
|
||||
token: string,
|
||||
type: number = 0,
|
||||
): Promise<TypesApiAkEndfield.AccSrvUserOAuth2V2Grant> => {
|
||||
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;
|
||||
},
|
||||
},
|
||||
},
|
||||
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;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -15,8 +15,12 @@ type ConfigType = AllRequired<
|
||||
network: {
|
||||
api: {
|
||||
akEndfield: {
|
||||
appCode: { osWinRel: string };
|
||||
launcherAppCode: { osWinRel: string };
|
||||
appCode: {
|
||||
game: { osWinRel: string };
|
||||
launcher: { osWinRel: string };
|
||||
accountService: { osWinRel: string };
|
||||
u8: { osWinRel: string };
|
||||
};
|
||||
channel: { osWinRel: number };
|
||||
base: {
|
||||
accountService: string;
|
||||
@@ -55,8 +59,12 @@ const initialConfig: ConfigType = {
|
||||
network: {
|
||||
api: {
|
||||
akEndfield: {
|
||||
appCode: { osWinRel: 'YDUTE5gscDZ229CW' },
|
||||
launcherAppCode: { osWinRel: 'TiaytKBUIEdoEwRT' },
|
||||
appCode: {
|
||||
game: { osWinRel: 'YDUTE5gscDZ229CW' },
|
||||
launcher: { osWinRel: 'TiaytKBUIEdoEwRT' },
|
||||
accountService: { osWinRel: 'd9f6dbb6bbd6bb33' },
|
||||
u8: { osWinRel: '973bd727dd11cbb6ead8' },
|
||||
},
|
||||
channel: { osWinRel: 6 },
|
||||
base: {
|
||||
accountService: 'YXMuZ3J5cGhsaW5lLmNvbQ==',
|
||||
|
||||
148
src/utils/string.ts
Normal file
148
src/utils/string.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
// === Generated by Qwen3-Max ===
|
||||
|
||||
/**
|
||||
* 元となるURLと相対URLから絶対URLを解決します。
|
||||
* ベースURLがファイルパスを含む場合でも、そのファイルのディレクトリを基準に解決します。
|
||||
*
|
||||
* @param baseUrl - 元となるURL(絶対URL)
|
||||
* @param relativeUrl - 相対URLまたは絶対URL
|
||||
* @returns 解決された絶対URL
|
||||
* @throws URLが無効な場合にエラーをスローします
|
||||
*/
|
||||
function resolveUrl(baseUrl: string, relativeUrl: string): string {
|
||||
try {
|
||||
// 相対URLがすでに絶対URLの場合、そのまま返す
|
||||
if (isAbsoluteUrl(relativeUrl)) {
|
||||
return new URL(relativeUrl).href;
|
||||
}
|
||||
|
||||
// baseUrlをURLとしてパース
|
||||
const base = new URL(baseUrl);
|
||||
|
||||
// ベースURLがファイルパス(末尾が/でない)の場合、
|
||||
// ファイル名を除去してディレクトリパスにする
|
||||
if (!base.pathname.endsWith('/')) {
|
||||
// 最後のスラッシュ以降を除去(ファイル名を削除)
|
||||
const lastSlashIndex = base.pathname.lastIndexOf('/');
|
||||
if (lastSlashIndex !== -1) {
|
||||
base.pathname = base.pathname.substring(0, lastSlashIndex + 1);
|
||||
} else {
|
||||
// パスにスラッシュが含まれていない稀なケース
|
||||
base.pathname = '/';
|
||||
}
|
||||
}
|
||||
|
||||
// 相対URLを解決
|
||||
const resolved = new URL(relativeUrl, base);
|
||||
|
||||
return resolved.href;
|
||||
} catch (error) {
|
||||
throw new Error(`URLの解決に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* URLが絶対URLかどうかを判定します。
|
||||
*
|
||||
* @param url - 判定するURL文字列
|
||||
* @returns 絶対URLの場合はtrue、それ以外はfalse
|
||||
*/
|
||||
function isAbsoluteUrl(url: string): boolean {
|
||||
try {
|
||||
return Boolean(new URL(url).protocol);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* URLフルパスからファイル名を除いた部分を抽出します
|
||||
* @param url
|
||||
* @returns
|
||||
*/
|
||||
function getBaseUrlWithoutLastSegment(url: string): string {
|
||||
const u = new URL(url);
|
||||
const dirPath = u.pathname.split('/').slice(0, -1).join('/');
|
||||
return `${u.origin}${dirPath}`;
|
||||
}
|
||||
|
||||
// ==============================
|
||||
|
||||
function replaceMultiPatterns(replacements: [RegExp, string][], originalString: string): string {
|
||||
return replacements.reduce((currentString, [pattern, replacement]) => {
|
||||
return currentString.replace(pattern, replacement);
|
||||
}, originalString);
|
||||
}
|
||||
|
||||
function sanitizeFilename(filename: string) {
|
||||
const invalidCharsMap: Record<string, string> = {
|
||||
'\\': '\',
|
||||
'/': '/',
|
||||
':': ':',
|
||||
'*': '*',
|
||||
'?': '?',
|
||||
'"': '"',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'|': '|',
|
||||
'&': '&',
|
||||
};
|
||||
return Array.from(filename)
|
||||
.map((char) => invalidCharsMap[char] || char)
|
||||
.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* 文字列が指定された正規表現パターンにマッチするかを判定する関数
|
||||
*
|
||||
* @param inputStr - チェック対象の文字列
|
||||
* @param regexInclude - マッチが必須の正規表現パターン(少なくとも1つマッチする必要あり)
|
||||
* @param regexExclude - マッチが禁止の正規表現パターン(1つもマッチしてはならない)
|
||||
* @returns
|
||||
* includeパターンが空の場合はexcludeにマッチしない限りtrue
|
||||
* includeパターンがある場合は、少なくとも1つのincludeにマッチし、
|
||||
* かつ全てのexcludeにマッチしない場合のみtrue
|
||||
*/
|
||||
function filterByRegex(
|
||||
inputStr: string,
|
||||
regexInclude: readonly (RegExp | string)[],
|
||||
regexExclude: readonly (RegExp | string)[],
|
||||
): boolean {
|
||||
// 文字列をRegExpに変換するヘルパー関数(グローバルフラグを無効化)
|
||||
const toRegExp = (pattern: RegExp | string): RegExp => {
|
||||
if (pattern instanceof RegExp) {
|
||||
// グローバルフラグ/ステッキーフラグを無効化(test()の状態問題を回避)
|
||||
const flags = pattern.flags.replace(/[gy]/g, '');
|
||||
return new RegExp(pattern.source, flags);
|
||||
}
|
||||
return new RegExp(pattern);
|
||||
};
|
||||
|
||||
// 正規表現を変換(include/exclude両方)
|
||||
const includePatterns = regexInclude.map(toRegExp);
|
||||
const excludePatterns = regexExclude.map(toRegExp);
|
||||
|
||||
// 1. excludeパターンに1つでもマッチしたら即座にfalse
|
||||
if (excludePatterns.some((pattern) => pattern.test(inputStr))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. includeパターンが空の場合はtrue(excludeを通過した時点でOK)
|
||||
if (includePatterns.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. includeパターンに1つでもマッチすればtrue
|
||||
return includePatterns.some((pattern) => pattern.test(inputStr));
|
||||
}
|
||||
|
||||
// ==============================
|
||||
|
||||
export default {
|
||||
resolveUrl,
|
||||
isAbsoluteUrl,
|
||||
getBaseUrlWithoutLastSegment,
|
||||
replaceMultiPatterns,
|
||||
sanitizeFilename,
|
||||
filterByRegex,
|
||||
};
|
||||
46
src/utils/termPretty.ts
Normal file
46
src/utils/termPretty.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
const cliTableConfig = {
|
||||
rounded: {
|
||||
chars: {
|
||||
top: '─',
|
||||
'top-mid': '',
|
||||
'top-left': '╭',
|
||||
'top-right': '╮',
|
||||
bottom: '─',
|
||||
'bottom-mid': '',
|
||||
'bottom-left': '╰',
|
||||
'bottom-right': '╯',
|
||||
left: '│',
|
||||
'left-mid': '├',
|
||||
mid: '─',
|
||||
'mid-mid': '',
|
||||
right: '│',
|
||||
'right-mid': '┤',
|
||||
middle: '',
|
||||
},
|
||||
style: { 'padding-left': 1, 'padding-right': 1, head: [''], border: [''], compact: true },
|
||||
},
|
||||
noBorder: {
|
||||
chars: {
|
||||
top: '',
|
||||
'top-mid': '',
|
||||
'top-left': '',
|
||||
'top-right': '',
|
||||
bottom: '',
|
||||
'bottom-mid': '',
|
||||
'bottom-left': '',
|
||||
'bottom-right': '',
|
||||
left: '',
|
||||
'left-mid': '',
|
||||
mid: '',
|
||||
'mid-mid': '',
|
||||
right: '',
|
||||
'right-mid': '',
|
||||
middle: ' ',
|
||||
},
|
||||
style: { 'padding-left': 0, 'padding-right': 0 },
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
cliTableConfig,
|
||||
};
|
||||
Reference in New Issue
Block a user