2022-10-21 07:09:30 -04:00
|
|
|
import { getBulletToken, getGToken, loginManually } from "./iksm.ts";
|
|
|
|
|
import { MultiProgressBar, Mutex } from "../deps.ts";
|
|
|
|
|
import {
|
2022-10-24 08:46:21 -04:00
|
|
|
DEFAULT_STATE,
|
|
|
|
|
FileStateBackend,
|
|
|
|
|
State,
|
|
|
|
|
StateBackend,
|
|
|
|
|
} from "./state.ts";
|
|
|
|
|
import {
|
2022-10-21 07:09:30 -04:00
|
|
|
getBankaraBattleHistories,
|
|
|
|
|
getBattleDetail,
|
|
|
|
|
getBattleList,
|
2022-10-24 14:45:36 -04:00
|
|
|
getCoopDetail,
|
|
|
|
|
getCoopHistories,
|
2022-10-24 08:46:21 -04:00
|
|
|
isTokenExpired,
|
2022-10-21 07:09:30 -04:00
|
|
|
} from "./splatnet3.ts";
|
|
|
|
|
import {
|
2022-10-24 14:45:36 -04:00
|
|
|
BattleListNode,
|
|
|
|
|
BattleListType,
|
2022-10-22 06:12:49 -04:00
|
|
|
ChallengeProgress,
|
2022-10-24 14:45:36 -04:00
|
|
|
CoopInfo,
|
|
|
|
|
CoopListNode,
|
|
|
|
|
Game,
|
|
|
|
|
GameExporter,
|
2022-10-21 07:09:30 -04:00
|
|
|
HistoryGroups,
|
2022-10-24 14:45:36 -04:00
|
|
|
VsInfo,
|
2022-10-21 07:09:30 -04:00
|
|
|
} from "./types.ts";
|
|
|
|
|
import { Cache, FileCache, MemoryCache } from "./cache.ts";
|
|
|
|
|
import { StatInkExporter } from "./exporters/stat.ink.ts";
|
|
|
|
|
import { FileExporter } from "./exporters/file.ts";
|
2022-10-24 08:46:21 -04:00
|
|
|
import {
|
|
|
|
|
delay,
|
2022-10-24 14:45:36 -04:00
|
|
|
gameId,
|
2022-10-24 08:46:21 -04:00
|
|
|
readline,
|
|
|
|
|
RecoverableError,
|
|
|
|
|
retryRecoverableError,
|
|
|
|
|
showError,
|
|
|
|
|
} from "./utils.ts";
|
2022-10-21 07:09:30 -04:00
|
|
|
|
|
|
|
|
export type Opts = {
|
|
|
|
|
profilePath: string;
|
|
|
|
|
exporter: string;
|
|
|
|
|
noProgress: boolean;
|
2022-10-22 09:00:45 -04:00
|
|
|
monitor: boolean;
|
2022-10-24 14:45:36 -04:00
|
|
|
skipMode?: string;
|
2022-10-23 04:00:16 -04:00
|
|
|
cache?: Cache;
|
2022-10-23 04:25:10 -04:00
|
|
|
stateBackend?: StateBackend;
|
2022-10-21 07:09:30 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const DEFAULT_OPTS: Opts = {
|
|
|
|
|
profilePath: "./profile.json",
|
|
|
|
|
exporter: "stat.ink",
|
|
|
|
|
noProgress: false,
|
2022-10-22 09:00:45 -04:00
|
|
|
monitor: false,
|
2022-10-21 07:09:30 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
2022-10-24 14:45:36 -04:00
|
|
|
* Fetch game and cache it.
|
2022-10-21 07:09:30 -04:00
|
|
|
*/
|
2022-10-24 14:45:36 -04:00
|
|
|
class GameFetcher {
|
2022-10-21 07:09:30 -04:00
|
|
|
state: State;
|
|
|
|
|
cache: Cache;
|
|
|
|
|
lock: Record<string, Mutex | undefined> = {};
|
|
|
|
|
bankaraLock = new Mutex();
|
2022-10-24 14:45:36 -04:00
|
|
|
bankaraHistory?: HistoryGroups<BattleListNode>["nodes"];
|
|
|
|
|
coopLock = new Mutex();
|
|
|
|
|
coopHistory?: HistoryGroups<CoopListNode>["nodes"];
|
2022-10-21 07:09:30 -04:00
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
|
{ cache = new MemoryCache(), state }: { state: State; cache?: Cache },
|
|
|
|
|
) {
|
|
|
|
|
this.state = state;
|
|
|
|
|
this.cache = cache;
|
|
|
|
|
}
|
|
|
|
|
private async getLock(id: string): Promise<Mutex> {
|
2022-10-24 14:45:36 -04:00
|
|
|
const bid = await gameId(id);
|
2022-10-21 07:09:30 -04:00
|
|
|
|
|
|
|
|
let cur = this.lock[bid];
|
|
|
|
|
if (!cur) {
|
|
|
|
|
cur = new Mutex();
|
|
|
|
|
this.lock[bid] = cur;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cur;
|
|
|
|
|
}
|
2022-10-24 14:45:36 -04:00
|
|
|
|
2022-10-21 07:09:30 -04:00
|
|
|
getBankaraHistory() {
|
|
|
|
|
return this.bankaraLock.use(async () => {
|
|
|
|
|
if (this.bankaraHistory) {
|
|
|
|
|
return this.bankaraHistory;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { bankaraBattleHistories: { historyGroups } } =
|
|
|
|
|
await getBankaraBattleHistories(
|
|
|
|
|
this.state,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
this.bankaraHistory = historyGroups.nodes;
|
|
|
|
|
|
|
|
|
|
return this.bankaraHistory;
|
|
|
|
|
});
|
|
|
|
|
}
|
2022-10-24 14:45:36 -04:00
|
|
|
getCoopHistory() {
|
|
|
|
|
return this.coopLock.use(async () => {
|
|
|
|
|
if (this.coopHistory) {
|
|
|
|
|
return this.coopHistory;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { coopResult: { historyGroups } } = await getCoopHistories(
|
|
|
|
|
this.state,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
this.coopHistory = historyGroups.nodes;
|
|
|
|
|
|
|
|
|
|
return this.coopHistory;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
async getCoopMetaById(id: string): Promise<Omit<CoopInfo, "detail">> {
|
|
|
|
|
const coopHistory = await this.getCoopHistory();
|
|
|
|
|
const group = coopHistory.find((i) =>
|
|
|
|
|
i.historyDetails.nodes.some((i) => i.id === id)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!group) {
|
|
|
|
|
return {
|
|
|
|
|
type: "CoopInfo",
|
|
|
|
|
listNode: null,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const listNode = group.historyDetails.nodes.find((i) => i.id === id) ??
|
|
|
|
|
null;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
type: "CoopInfo",
|
|
|
|
|
listNode,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
async getBattleMetaById(id: string): Promise<Omit<VsInfo, "detail">> {
|
|
|
|
|
const bid = await gameId(id);
|
2022-10-21 07:09:30 -04:00
|
|
|
const bankaraHistory = await this.getBankaraHistory();
|
|
|
|
|
const group = bankaraHistory.find((i) =>
|
|
|
|
|
i.historyDetails.nodes.some((i) => i._bid === bid)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!group) {
|
|
|
|
|
return {
|
2022-10-24 14:45:36 -04:00
|
|
|
type: "VsInfo",
|
2022-10-22 06:12:49 -04:00
|
|
|
challengeProgress: null,
|
2022-10-21 07:09:30 -04:00
|
|
|
bankaraMatchChallenge: null,
|
|
|
|
|
listNode: null,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { bankaraMatchChallenge } = group;
|
|
|
|
|
const listNode = group.historyDetails.nodes.find((i) => i._bid === bid) ??
|
|
|
|
|
null;
|
2022-10-22 06:12:49 -04:00
|
|
|
const index = group.historyDetails.nodes.indexOf(listNode!);
|
|
|
|
|
|
|
|
|
|
let challengeProgress: null | ChallengeProgress = null;
|
|
|
|
|
if (bankaraMatchChallenge) {
|
|
|
|
|
const pastBattles = group.historyDetails.nodes.slice(0, index);
|
|
|
|
|
const { winCount, loseCount } = bankaraMatchChallenge;
|
|
|
|
|
challengeProgress = {
|
|
|
|
|
index,
|
|
|
|
|
winCount: winCount -
|
|
|
|
|
pastBattles.filter((i) => i.judgement == "WIN").length,
|
|
|
|
|
loseCount: loseCount -
|
|
|
|
|
pastBattles.filter((i) =>
|
|
|
|
|
["LOSE", "DEEMED_LOSE"].includes(i.judgement)
|
|
|
|
|
).length,
|
|
|
|
|
};
|
|
|
|
|
}
|
2022-10-21 07:09:30 -04:00
|
|
|
|
|
|
|
|
return {
|
2022-10-24 14:45:36 -04:00
|
|
|
type: "VsInfo",
|
2022-10-21 07:09:30 -04:00
|
|
|
bankaraMatchChallenge,
|
|
|
|
|
listNode,
|
2022-10-22 06:12:49 -04:00
|
|
|
challengeProgress,
|
2022-10-21 07:09:30 -04:00
|
|
|
};
|
|
|
|
|
}
|
2022-10-24 14:45:36 -04:00
|
|
|
async cacheDetail<T>(
|
|
|
|
|
id: string,
|
|
|
|
|
getter: () => Promise<T>,
|
|
|
|
|
): Promise<T> {
|
2022-10-21 07:09:30 -04:00
|
|
|
const lock = await this.getLock(id);
|
|
|
|
|
|
|
|
|
|
return lock.use(async () => {
|
2022-10-24 14:45:36 -04:00
|
|
|
const cached = await this.cache.read<T>(id);
|
2022-10-21 07:09:30 -04:00
|
|
|
if (cached) {
|
|
|
|
|
return cached;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-24 14:45:36 -04:00
|
|
|
const detail = await getter();
|
2022-10-21 07:09:30 -04:00
|
|
|
|
|
|
|
|
await this.cache.write(id, detail);
|
|
|
|
|
|
|
|
|
|
return detail;
|
|
|
|
|
});
|
|
|
|
|
}
|
2022-10-24 14:45:36 -04:00
|
|
|
fetch(type: Game["type"], id: string): Promise<Game> {
|
|
|
|
|
switch (type) {
|
|
|
|
|
case "VsInfo":
|
|
|
|
|
return this.fetchBattle(id);
|
|
|
|
|
case "CoopInfo":
|
|
|
|
|
return this.fetchCoop(id);
|
|
|
|
|
default:
|
|
|
|
|
throw new Error(`Unknown game type: ${type}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
async fetchBattle(id: string): Promise<VsInfo> {
|
|
|
|
|
const detail = await this.cacheDetail(
|
|
|
|
|
id,
|
|
|
|
|
() => getBattleDetail(this.state, id).then((r) => r.vsHistoryDetail),
|
|
|
|
|
);
|
2022-10-21 07:09:30 -04:00
|
|
|
const metadata = await this.getBattleMetaById(id);
|
|
|
|
|
|
2022-10-24 14:45:36 -04:00
|
|
|
const game: VsInfo = {
|
|
|
|
|
...metadata,
|
|
|
|
|
detail,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return game;
|
|
|
|
|
}
|
|
|
|
|
async fetchCoop(id: string): Promise<CoopInfo> {
|
|
|
|
|
const detail = await this.cacheDetail(
|
|
|
|
|
id,
|
|
|
|
|
() => getCoopDetail(this.state, id).then((r) => r.coopHistoryDetail),
|
|
|
|
|
);
|
|
|
|
|
const metadata = await this.getCoopMetaById(id);
|
|
|
|
|
|
|
|
|
|
const game: CoopInfo = {
|
2022-10-21 07:09:30 -04:00
|
|
|
...metadata,
|
|
|
|
|
detail,
|
|
|
|
|
};
|
|
|
|
|
|
2022-10-24 14:45:36 -04:00
|
|
|
return game;
|
2022-10-21 07:09:30 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Progress = {
|
|
|
|
|
current: number;
|
|
|
|
|
total: number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export class App {
|
|
|
|
|
state: State = DEFAULT_STATE;
|
2022-10-23 04:25:10 -04:00
|
|
|
stateBackend: StateBackend;
|
2022-10-24 08:46:21 -04:00
|
|
|
recoveryToken: RecoverableError = {
|
|
|
|
|
name: "Refetch Token",
|
|
|
|
|
is: isTokenExpired,
|
|
|
|
|
recovery: async () => {
|
|
|
|
|
console.log("Token expired, refetch tokens.");
|
|
|
|
|
|
|
|
|
|
await this.fetchToken();
|
|
|
|
|
},
|
|
|
|
|
};
|
2022-10-21 07:09:30 -04:00
|
|
|
|
|
|
|
|
constructor(public opts: Opts) {
|
2022-10-24 08:46:21 -04:00
|
|
|
this.stateBackend = opts.stateBackend ??
|
|
|
|
|
new FileStateBackend(opts.profilePath);
|
2022-10-21 07:09:30 -04:00
|
|
|
}
|
|
|
|
|
async writeState(newState: State) {
|
|
|
|
|
this.state = newState;
|
2022-10-23 04:25:10 -04:00
|
|
|
await this.stateBackend.write(newState);
|
2022-10-21 07:09:30 -04:00
|
|
|
}
|
|
|
|
|
async readState() {
|
|
|
|
|
try {
|
2022-10-23 04:25:10 -04:00
|
|
|
const json = await this.stateBackend.read();
|
2022-10-21 07:09:30 -04:00
|
|
|
this.state = {
|
|
|
|
|
...DEFAULT_STATE,
|
|
|
|
|
...json,
|
|
|
|
|
};
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn(
|
|
|
|
|
`Failed to read config file, create new config file. (${e})`,
|
|
|
|
|
);
|
|
|
|
|
await this.writeState(DEFAULT_STATE);
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-10-24 14:45:36 -04:00
|
|
|
getSkipMode(): ("vs" | "coop")[] {
|
|
|
|
|
const mode = this.opts.skipMode;
|
|
|
|
|
if (mode === "vs") {
|
|
|
|
|
return ["vs"];
|
|
|
|
|
} else if (mode === "coop") {
|
|
|
|
|
return ["coop"];
|
|
|
|
|
}
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
async getExporters(): Promise<GameExporter[]> {
|
2022-10-21 07:09:30 -04:00
|
|
|
const exporters = this.opts.exporter.split(",");
|
2022-10-24 14:45:36 -04:00
|
|
|
const out: GameExporter[] = [];
|
2022-10-21 07:09:30 -04:00
|
|
|
|
|
|
|
|
if (exporters.includes("stat.ink")) {
|
|
|
|
|
if (!this.state.statInkApiKey) {
|
|
|
|
|
console.log("stat.ink API key is not set. Please enter below.");
|
|
|
|
|
const key = (await readline()).trim();
|
|
|
|
|
if (!key) {
|
|
|
|
|
console.error("API key is required.");
|
|
|
|
|
Deno.exit(1);
|
|
|
|
|
}
|
|
|
|
|
await this.writeState({
|
|
|
|
|
...this.state,
|
|
|
|
|
statInkApiKey: key,
|
|
|
|
|
});
|
|
|
|
|
}
|
2022-10-22 18:52:08 -04:00
|
|
|
out.push(
|
|
|
|
|
new StatInkExporter(
|
|
|
|
|
this.state.statInkApiKey!,
|
|
|
|
|
this.opts.monitor ? "Monitoring" : "Manual",
|
|
|
|
|
),
|
|
|
|
|
);
|
2022-10-21 07:09:30 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (exporters.includes("file")) {
|
|
|
|
|
out.push(new FileExporter(this.state.fileExportPath));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return out;
|
|
|
|
|
}
|
2022-10-24 08:46:21 -04:00
|
|
|
exportOnce() {
|
|
|
|
|
return retryRecoverableError(() => this._exportOnce(), this.recoveryToken);
|
|
|
|
|
}
|
2022-10-24 14:45:36 -04:00
|
|
|
exporterProgress(title: string) {
|
2022-10-21 07:09:30 -04:00
|
|
|
const bar = !this.opts.noProgress
|
|
|
|
|
? new MultiProgressBar({
|
2022-10-24 14:45:36 -04:00
|
|
|
title,
|
2022-10-21 07:09:30 -04:00
|
|
|
display: "[:bar] :text :percent :time eta: :eta :completed/:total",
|
|
|
|
|
})
|
|
|
|
|
: undefined;
|
|
|
|
|
|
2022-10-24 14:45:36 -04:00
|
|
|
const allProgress: Record<string, Progress> = {};
|
|
|
|
|
const redraw = (name: string, progress: Progress) => {
|
|
|
|
|
allProgress[name] = progress;
|
|
|
|
|
bar?.render(
|
|
|
|
|
Object.entries(allProgress).map(([name, progress]) => ({
|
|
|
|
|
completed: progress.current,
|
|
|
|
|
total: progress.total,
|
|
|
|
|
text: name,
|
|
|
|
|
})),
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
const endBar = () => {
|
|
|
|
|
bar?.end();
|
|
|
|
|
};
|
2022-10-21 07:09:30 -04:00
|
|
|
|
2022-10-24 14:45:36 -04:00
|
|
|
return { redraw, endBar };
|
|
|
|
|
}
|
|
|
|
|
async _exportOnce() {
|
|
|
|
|
const exporters = await this.getExporters();
|
|
|
|
|
const skipMode = this.getSkipMode();
|
|
|
|
|
const stats: Record<string, number> = Object.fromEntries(
|
|
|
|
|
exporters.map((e) => [e.name, 0]),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (skipMode.includes("vs")) {
|
|
|
|
|
console.log("Skip exporting VS games.");
|
|
|
|
|
} else {
|
|
|
|
|
console.log("Fetching battle list...");
|
|
|
|
|
const gameList = await getBattleList(this.state);
|
|
|
|
|
|
|
|
|
|
const { redraw, endBar } = this.exporterProgress("Export games");
|
|
|
|
|
const fetcher = new GameFetcher({
|
2022-10-24 08:46:21 -04:00
|
|
|
cache: this.opts.cache ?? new FileCache(this.state.cacheDir),
|
|
|
|
|
state: this.state,
|
|
|
|
|
});
|
2022-10-21 07:09:30 -04:00
|
|
|
|
2022-10-24 08:46:21 -04:00
|
|
|
await Promise.all(
|
|
|
|
|
exporters.map((e) =>
|
|
|
|
|
showError(
|
2022-10-24 14:45:36 -04:00
|
|
|
this.exportGameList({
|
|
|
|
|
type: "VsInfo",
|
2022-10-24 08:46:21 -04:00
|
|
|
fetcher,
|
|
|
|
|
exporter: e,
|
2022-10-24 14:45:36 -04:00
|
|
|
gameList,
|
2022-10-24 08:46:21 -04:00
|
|
|
onStep: (progress) => redraw(e.name, progress),
|
|
|
|
|
})
|
|
|
|
|
.then((count) => {
|
|
|
|
|
stats[e.name] = count;
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.catch((err) => {
|
|
|
|
|
console.error(`\nFailed to export to ${e.name}:`, err);
|
|
|
|
|
})
|
|
|
|
|
),
|
|
|
|
|
);
|
2022-10-21 07:09:30 -04:00
|
|
|
|
2022-10-24 14:45:36 -04:00
|
|
|
endBar();
|
|
|
|
|
}
|
2022-10-22 18:52:08 -04:00
|
|
|
|
2022-10-24 14:45:36 -04:00
|
|
|
if (skipMode.includes("coop")) {
|
|
|
|
|
console.log("Skip exporting Coop games.");
|
|
|
|
|
} else {
|
|
|
|
|
console.log("Fetching coop battle list...");
|
|
|
|
|
const coopBattleList = await getBattleList(
|
|
|
|
|
this.state,
|
|
|
|
|
BattleListType.Coop,
|
2022-10-24 08:46:21 -04:00
|
|
|
);
|
2022-10-24 14:45:36 -04:00
|
|
|
|
|
|
|
|
const { redraw, endBar } = this.exporterProgress("Export games");
|
|
|
|
|
const fetcher = new GameFetcher({
|
|
|
|
|
cache: this.opts.cache ?? new FileCache(this.state.cacheDir),
|
|
|
|
|
state: this.state,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await Promise.all(
|
|
|
|
|
// TODO: remove this filter when stat.ink support coop export
|
|
|
|
|
exporters.filter((e) => e.name !== "stat.ink").map((e) =>
|
|
|
|
|
showError(
|
|
|
|
|
this.exportGameList({
|
|
|
|
|
type: "CoopInfo",
|
|
|
|
|
fetcher,
|
|
|
|
|
exporter: e,
|
|
|
|
|
gameList: coopBattleList,
|
|
|
|
|
onStep: (progress) => redraw(e.name, progress),
|
|
|
|
|
})
|
|
|
|
|
.then((count) => {
|
|
|
|
|
stats[e.name] = count;
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.catch((err) => {
|
|
|
|
|
console.error(`\nFailed to export to ${e.name}:`, err);
|
|
|
|
|
})
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
endBar();
|
2022-10-24 08:46:21 -04:00
|
|
|
}
|
2022-10-24 14:45:36 -04:00
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
`Exported ${
|
|
|
|
|
Object.entries(stats)
|
|
|
|
|
.map(([name, count]) => `${name}: ${count}`)
|
|
|
|
|
.join(", ")
|
|
|
|
|
}`,
|
|
|
|
|
);
|
2022-10-22 18:52:08 -04:00
|
|
|
}
|
|
|
|
|
async monitor() {
|
|
|
|
|
while (true) {
|
|
|
|
|
await this.exportOnce();
|
|
|
|
|
await this.countDown(this.state.monitorInterval);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
async countDown(sec: number) {
|
|
|
|
|
const bar = !this.opts.noProgress
|
|
|
|
|
? new MultiProgressBar({
|
|
|
|
|
title: "Killing time...",
|
|
|
|
|
display: "[:bar] :completed/:total",
|
|
|
|
|
})
|
|
|
|
|
: undefined;
|
|
|
|
|
for (const i of Array(sec).keys()) {
|
|
|
|
|
bar?.render([{
|
|
|
|
|
completed: i,
|
|
|
|
|
total: sec,
|
|
|
|
|
}]);
|
|
|
|
|
await delay(1000);
|
|
|
|
|
}
|
|
|
|
|
bar?.end();
|
|
|
|
|
}
|
2022-10-24 08:46:21 -04:00
|
|
|
async fetchToken() {
|
|
|
|
|
const sessionToken = this.state.loginState?.sessionToken;
|
|
|
|
|
|
|
|
|
|
if (!sessionToken) {
|
|
|
|
|
throw new Error("Session token is not set.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { webServiceToken, userCountry, userLang } = await getGToken({
|
|
|
|
|
fApi: this.state.fGen,
|
|
|
|
|
sessionToken,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const bulletToken = await getBulletToken({
|
|
|
|
|
webServiceToken,
|
|
|
|
|
userLang,
|
|
|
|
|
userCountry,
|
|
|
|
|
appUserAgent: this.state.appUserAgent,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await this.writeState({
|
|
|
|
|
...this.state,
|
|
|
|
|
loginState: {
|
|
|
|
|
...this.state.loginState,
|
|
|
|
|
gToken: webServiceToken,
|
|
|
|
|
bulletToken,
|
|
|
|
|
},
|
|
|
|
|
userLang: this.state.userLang ?? userLang,
|
|
|
|
|
userCountry: this.state.userCountry ?? userCountry,
|
|
|
|
|
});
|
|
|
|
|
}
|
2022-10-22 18:52:08 -04:00
|
|
|
async run() {
|
|
|
|
|
await this.readState();
|
|
|
|
|
|
|
|
|
|
if (!this.state.loginState?.sessionToken) {
|
|
|
|
|
const sessionToken = await loginManually();
|
|
|
|
|
|
|
|
|
|
await this.writeState({
|
|
|
|
|
...this.state,
|
|
|
|
|
loginState: {
|
|
|
|
|
...this.state.loginState,
|
|
|
|
|
sessionToken,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.opts.monitor) {
|
|
|
|
|
await this.monitor();
|
|
|
|
|
} else {
|
|
|
|
|
await this.exportOnce();
|
|
|
|
|
}
|
2022-10-21 07:09:30 -04:00
|
|
|
}
|
|
|
|
|
/**
|
2022-10-24 14:45:36 -04:00
|
|
|
* Export game list.
|
2022-10-21 07:09:30 -04:00
|
|
|
*
|
|
|
|
|
* @param fetcher BattleFetcher
|
|
|
|
|
* @param exporter BattleExporter
|
2022-10-24 14:45:36 -04:00
|
|
|
* @param gameList ID list of games, sorted by date, newest first
|
|
|
|
|
* @param onStep Callback function called when a game is exported
|
2022-10-21 07:09:30 -04:00
|
|
|
*/
|
2022-10-24 14:45:36 -04:00
|
|
|
async exportGameList({
|
|
|
|
|
type,
|
|
|
|
|
fetcher,
|
|
|
|
|
exporter,
|
|
|
|
|
gameList,
|
|
|
|
|
onStep,
|
|
|
|
|
}: {
|
|
|
|
|
type: Game["type"];
|
|
|
|
|
exporter: GameExporter;
|
|
|
|
|
fetcher: GameFetcher;
|
|
|
|
|
gameList: string[];
|
|
|
|
|
onStep: (progress: Progress) => void;
|
|
|
|
|
}) {
|
2022-10-21 07:09:30 -04:00
|
|
|
let exported = 0;
|
|
|
|
|
|
|
|
|
|
onStep?.({
|
|
|
|
|
current: 0,
|
|
|
|
|
total: 1,
|
|
|
|
|
});
|
|
|
|
|
|
2022-10-24 14:45:36 -04:00
|
|
|
const workQueue = [
|
|
|
|
|
...await exporter.notExported({
|
|
|
|
|
type,
|
|
|
|
|
list: gameList,
|
|
|
|
|
}),
|
|
|
|
|
]
|
|
|
|
|
.reverse();
|
|
|
|
|
|
|
|
|
|
const step = async (id: string) => {
|
|
|
|
|
const detail = await fetcher.fetch(type, id);
|
|
|
|
|
await exporter.exportGame(detail);
|
2022-10-21 07:09:30 -04:00
|
|
|
exported += 1;
|
|
|
|
|
onStep?.({
|
|
|
|
|
current: exported,
|
|
|
|
|
total: workQueue.length,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (workQueue.length > 0) {
|
|
|
|
|
onStep?.({
|
|
|
|
|
current: exported,
|
|
|
|
|
total: workQueue.length,
|
|
|
|
|
});
|
|
|
|
|
for (const battle of workQueue) {
|
|
|
|
|
await step(battle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return exported;
|
|
|
|
|
}
|
|
|
|
|
}
|