feat: add init rank
parent
a5c68ad06b
commit
297e73e197
|
|
@ -0,0 +1 @@
|
||||||
|
export { assertEquals } from "https://deno.land/std@0.160.0/testing/asserts.ts";
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
/**
|
||||||
|
* If rankState in profile.json is not defined, it will be initialized.
|
||||||
|
*/
|
||||||
|
import { flags } from "./deps.ts";
|
||||||
|
import { getBulletToken, getGToken } from "./src/iksm.ts";
|
||||||
|
import { checkToken, getBattleDetail, getBattleList } from "./src/splatnet3.ts";
|
||||||
|
import { gameId, readline } from "./src/utils.ts";
|
||||||
|
import { FileStateBackend } from "./src/state.ts";
|
||||||
|
import { BattleListType } from "./src/types.ts";
|
||||||
|
import { RANK_PARAMS } from "./src/RankTracker.ts";
|
||||||
|
|
||||||
|
const parseArgs = (args: string[]) => {
|
||||||
|
const parsed = flags.parse(args, {
|
||||||
|
string: ["profilePath"],
|
||||||
|
alias: {
|
||||||
|
"help": "h",
|
||||||
|
"profilePath": ["p", "profile-path"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return parsed;
|
||||||
|
};
|
||||||
|
|
||||||
|
const opts = parseArgs(Deno.args);
|
||||||
|
if (opts.help) {
|
||||||
|
console.log(
|
||||||
|
`Usage: deno run -A ${Deno.mainModule} [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--profile-path <path>, -p Path to config file (default: ./profile.json)
|
||||||
|
--help Show this help message and exit`,
|
||||||
|
);
|
||||||
|
Deno.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const stateBackend = new FileStateBackend(opts.profilePath ?? "./profile.json");
|
||||||
|
let state = await stateBackend.read();
|
||||||
|
|
||||||
|
if (state.rankState) {
|
||||||
|
console.log("rankState is already initialized.");
|
||||||
|
Deno.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await checkToken(state)) {
|
||||||
|
const sessionToken = state.loginState?.sessionToken;
|
||||||
|
|
||||||
|
if (!sessionToken) {
|
||||||
|
throw new Error("Session token is not set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { webServiceToken, userCountry, userLang } = await getGToken({
|
||||||
|
fApi: state.fGen,
|
||||||
|
sessionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const bulletToken = await getBulletToken({
|
||||||
|
webServiceToken,
|
||||||
|
userLang,
|
||||||
|
userCountry,
|
||||||
|
appUserAgent: state.appUserAgent,
|
||||||
|
});
|
||||||
|
|
||||||
|
state = {
|
||||||
|
...state,
|
||||||
|
loginState: {
|
||||||
|
...state.loginState,
|
||||||
|
gToken: webServiceToken,
|
||||||
|
bulletToken,
|
||||||
|
},
|
||||||
|
userLang: state.userLang ?? userLang,
|
||||||
|
userCountry: state.userCountry ?? userCountry,
|
||||||
|
};
|
||||||
|
await stateBackend.write(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
const battleList = await getBattleList(state, BattleListType.Bankara);
|
||||||
|
if (battleList.length === 0) {
|
||||||
|
console.log("No anarchy battle found. Did you play anarchy?");
|
||||||
|
Deno.exit(0);
|
||||||
|
}
|
||||||
|
const { vsHistoryDetail: detail } = await getBattleDetail(state, battleList[0]);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Your latest battle is played at ${
|
||||||
|
new Date(detail.playedTime).toLocaleString()
|
||||||
|
}. Please enter your rank after this battle(format: RANK,POINT. S+0,300):`,
|
||||||
|
);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const userInput = await readline();
|
||||||
|
const [rank, point] = userInput.split(",");
|
||||||
|
const pointNumber = parseInt(point);
|
||||||
|
|
||||||
|
if (!RANK_PARAMS.find((i) => i.rank === rank)) {
|
||||||
|
console.log("Invalid rank. Please enter again:");
|
||||||
|
} else if (isNaN(pointNumber)) {
|
||||||
|
console.log("Invalid point. Please enter again:");
|
||||||
|
} else {
|
||||||
|
state = {
|
||||||
|
...state,
|
||||||
|
rankState: {
|
||||||
|
gameId: await gameId(detail.id),
|
||||||
|
rank,
|
||||||
|
rankPoint: pointNumber,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await stateBackend.write(state);
|
||||||
|
console.log("rankState is initialized.");
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { RankTracker } from "./RankTracker.ts";
|
||||||
|
import { assertEquals } from "../dev_deps.ts";
|
||||||
|
|
||||||
|
Deno.test("RankTracker", () => {
|
||||||
|
const tracker = new RankTracker();
|
||||||
|
assertEquals(tracker, new RankTracker());
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
import { RankState } from "./state.ts";
|
||||||
|
import { GameFetcher } from "./GameFetcher.ts";
|
||||||
|
|
||||||
|
type RankParam = {
|
||||||
|
rank: string;
|
||||||
|
pointRange: [number, number];
|
||||||
|
entrance: number;
|
||||||
|
openWin: number;
|
||||||
|
openLose: number;
|
||||||
|
rankUp?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const splusParams = () => {
|
||||||
|
const out: RankParam[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < 50; i++) {
|
||||||
|
const level = i % 10;
|
||||||
|
const item: RankParam = {
|
||||||
|
rank: `S+${i}`,
|
||||||
|
pointRange: [300 + level * 350, 300 + (level + 1) * 350],
|
||||||
|
entrance: 160,
|
||||||
|
openWin: 8,
|
||||||
|
openLose: 5,
|
||||||
|
};
|
||||||
|
if (level === 9) {
|
||||||
|
item.rankUp = true;
|
||||||
|
}
|
||||||
|
out.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push({
|
||||||
|
rank: "S+50",
|
||||||
|
pointRange: [0, 9999],
|
||||||
|
entrance: 160,
|
||||||
|
openWin: 8,
|
||||||
|
openLose: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RANK_PARAMS: RankParam[] = [{
|
||||||
|
rank: "C-",
|
||||||
|
pointRange: [0, 200],
|
||||||
|
entrance: 0,
|
||||||
|
openWin: 8,
|
||||||
|
openLose: 1,
|
||||||
|
}, {
|
||||||
|
rank: "C",
|
||||||
|
pointRange: [200, 400],
|
||||||
|
entrance: 20,
|
||||||
|
openWin: 8,
|
||||||
|
openLose: 1,
|
||||||
|
}, {
|
||||||
|
rank: "C+",
|
||||||
|
pointRange: [400, 600],
|
||||||
|
entrance: 40,
|
||||||
|
openWin: 8,
|
||||||
|
openLose: 1,
|
||||||
|
rankUp: true,
|
||||||
|
}, {
|
||||||
|
rank: "B-",
|
||||||
|
pointRange: [100, 350],
|
||||||
|
entrance: 55,
|
||||||
|
openWin: 8,
|
||||||
|
openLose: 2,
|
||||||
|
}, {
|
||||||
|
rank: "B",
|
||||||
|
pointRange: [350, 600],
|
||||||
|
entrance: 70,
|
||||||
|
openWin: 8,
|
||||||
|
openLose: 2,
|
||||||
|
}, {
|
||||||
|
rank: "B+",
|
||||||
|
pointRange: [600, 850],
|
||||||
|
entrance: 85,
|
||||||
|
openWin: 8,
|
||||||
|
openLose: 2,
|
||||||
|
rankUp: true,
|
||||||
|
}, {
|
||||||
|
rank: "A-",
|
||||||
|
pointRange: [200, 500],
|
||||||
|
entrance: 100,
|
||||||
|
openWin: 8,
|
||||||
|
openLose: 3,
|
||||||
|
}, {
|
||||||
|
rank: "A",
|
||||||
|
pointRange: [500, 800],
|
||||||
|
entrance: 110,
|
||||||
|
openWin: 8,
|
||||||
|
openLose: 3,
|
||||||
|
}, {
|
||||||
|
rank: "A+",
|
||||||
|
pointRange: [800, 1100],
|
||||||
|
entrance: 120,
|
||||||
|
openWin: 8,
|
||||||
|
openLose: 3,
|
||||||
|
rankUp: true,
|
||||||
|
}, {
|
||||||
|
rank: "S",
|
||||||
|
pointRange: [300, 1000],
|
||||||
|
entrance: 150,
|
||||||
|
openWin: 8,
|
||||||
|
openLose: 4,
|
||||||
|
rankUp: true,
|
||||||
|
}, ...splusParams()];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* if state is empty, it will not track rank.
|
||||||
|
*/
|
||||||
|
export class RankTracker {
|
||||||
|
constructor(private state?: RankState) {}
|
||||||
|
}
|
||||||
11
src/state.ts
11
src/state.ts
|
|
@ -3,6 +3,15 @@ export type LoginState = {
|
||||||
gToken?: string;
|
gToken?: string;
|
||||||
bulletToken?: string;
|
bulletToken?: string;
|
||||||
};
|
};
|
||||||
|
export type RankState = {
|
||||||
|
// generated by gameId(battle.id)
|
||||||
|
// If the gameId does not exist, the tracker will assume that the user has
|
||||||
|
// not played a bankara match. And it will start tracking from the first match
|
||||||
|
gameId?: string;
|
||||||
|
// C-, B, A+, S, S+0, S+12
|
||||||
|
rank: string;
|
||||||
|
rankPoint: number;
|
||||||
|
};
|
||||||
export type State = {
|
export type State = {
|
||||||
loginState?: LoginState;
|
loginState?: LoginState;
|
||||||
fGen: string;
|
fGen: string;
|
||||||
|
|
@ -10,6 +19,8 @@ export type State = {
|
||||||
userLang?: string;
|
userLang?: string;
|
||||||
userCountry?: string;
|
userCountry?: string;
|
||||||
|
|
||||||
|
rankState?: RankState;
|
||||||
|
|
||||||
cacheDir: string;
|
cacheDir: string;
|
||||||
|
|
||||||
// Exporter config
|
// Exporter config
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,11 @@ export function urlBase64Decode(data: string) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function readline() {
|
export async function readline(
|
||||||
|
{ skipEmpty = true }: { skipEmpty?: boolean } = {},
|
||||||
|
) {
|
||||||
for await (const line of stdinLines) {
|
for await (const line of stdinLines) {
|
||||||
if (line !== "") {
|
if (!skipEmpty || line !== "") {
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue