feat: implement auto list-method

dumb-splashcat-thing
spacemeowx2 2023-06-06 21:47:18 +08:00 committed by imspace
parent a5f35c78c9
commit 63ea9347da
3 changed files with 120 additions and 28 deletions

View File

@ -32,7 +32,8 @@ Options:
(e.g. "stat.ink,file") (e.g. "stat.ink,file")
--list-method When set to "latest", the latest 50 matches will be obtained. --list-method When set to "latest", the latest 50 matches will be obtained.
When set to "all", matches of all modes will be obtained with a maximum of 250 matches (5 modes x 50 matches). When set to "all", matches of all modes will be obtained with a maximum of 250 matches (5 modes x 50 matches).
"latest" is the default setting. When set to "auto", the latest 50 matches will be obtained. If 50 matches have not been uploaded yet, matches will be obtained from the list of all modes.
"auto" is the default setting.
--no-progress, -n Disable progress bar --no-progress, -n Disable progress bar
--monitor, -m Monitor mode --monitor, -m Monitor mode
--skip-mode <mode>, -s Skip mode (default: null) --skip-mode <mode>, -s Skip mode (default: null)

View File

@ -1,8 +1,8 @@
import { loginManually } from "./iksm.ts"; import { loginManually } from "./iksm.ts";
import { MultiProgressBar } from "../deps.ts"; import { MultiProgressBar, Mutex } from "../deps.ts";
import { FileStateBackend, Profile, StateBackend } from "./state.ts"; import { FileStateBackend, Profile, StateBackend } from "./state.ts";
import { Splatnet3 } from "./splatnet3.ts"; import { Splatnet3 } from "./splatnet3.ts";
import { BattleListType, Game, GameExporter } from "./types.ts"; import { BattleListType, Game, GameExporter, ListMethod } from "./types.ts";
import { Cache, FileCache } from "./cache.ts"; import { Cache, FileCache } from "./cache.ts";
import { StatInkExporter } from "./exporters/stat.ink.ts"; import { StatInkExporter } from "./exporters/stat.ink.ts";
import { FileExporter } from "./exporters/file.ts"; import { FileExporter } from "./exporters/file.ts";
@ -17,7 +17,7 @@ export type Opts = {
monitor: boolean; monitor: boolean;
withSummary: boolean; withSummary: boolean;
skipMode?: string; skipMode?: string;
listMethod: string; listMethod?: string;
cache?: Cache; cache?: Cache;
stateBackend?: StateBackend; stateBackend?: StateBackend;
env: Env; env: Env;
@ -54,6 +54,103 @@ class StepProgress {
} }
} }
interface GameListFetcher {
/**
* Return not exported game list.
* [0] is the latest game.
* @param exporter GameExporter
*/
fetch(exporter: GameExporter): Promise<string[]>;
}
class BattleListFetcher implements GameListFetcher {
protected listMethod: ListMethod;
protected allBattleList?: string[];
protected latestBattleList?: string[];
protected allLock = new Mutex();
protected latestLock = new Mutex();
constructor(
listMethod: string,
protected splatnet: Splatnet3,
) {
if (listMethod === "all") {
this.listMethod = "all";
} else if (listMethod === "latest") {
this.listMethod = "latest";
} else {
this.listMethod = "auto";
}
}
protected getAllBattleList() {
return this.allLock.use(async () => {
if (!this.allBattleList) {
this.allBattleList = await this.splatnet.getAllBattleList();
}
return this.allBattleList;
});
}
protected getLatestBattleList() {
return this.latestLock.use(async () => {
if (!this.latestBattleList) {
this.latestBattleList = await this.splatnet.getBattleList();
}
return this.latestBattleList;
});
}
private async innerFetch(exporter: GameExporter) {
if (this.listMethod === "latest") {
return await exporter.notExported({
type: "VsInfo",
list: await this.getLatestBattleList(),
});
}
if (this.listMethod === "all") {
return await exporter.notExported({
type: "VsInfo",
list: await this.getAllBattleList(),
});
}
if (this.listMethod === "auto") {
const latestList = await exporter.notExported({
type: "VsInfo",
list: await this.getLatestBattleList(),
});
if (latestList.length === 50) {
return await exporter.notExported({
type: "VsInfo",
list: await this.getAllBattleList(),
});
}
return latestList;
}
throw new TypeError(`Unknown listMethod: ${this.listMethod}`);
}
async fetch(exporter: GameExporter) {
return [...await this.innerFetch(exporter)].reverse();
}
}
class CoopListFetcher implements GameListFetcher {
constructor(
protected splatnet: Splatnet3,
) {}
async fetch(exporter: GameExporter) {
return [
...await exporter.notExported({
type: "CoopInfo",
list: await this.splatnet.getBattleList(BattleListType.Coop),
}),
].reverse();
}
}
function progress({ total, currentUrl, done }: StepProgress): Progress { function progress({ total, currentUrl, done }: StepProgress): Progress {
return { return {
total, total,
@ -74,6 +171,12 @@ export class App {
env: opts.env, env: opts.env,
}); });
this.env = opts.env; this.env = opts.env;
if (
opts.listMethod && !["all", "auto", "latest"].includes(opts.listMethod)
) {
throw new TypeError(`Unknown listMethod: ${opts.listMethod}`);
}
} }
getSkipMode(): ("vs" | "coop")[] { getSkipMode(): ("vs" | "coop")[] {
@ -164,13 +267,10 @@ export class App {
if (skipMode.includes("vs") || exporters.length === 0) { if (skipMode.includes("vs") || exporters.length === 0) {
this.env.logger.log("Skip exporting VS games."); this.env.logger.log("Skip exporting VS games.");
} else { } else {
this.env.logger.log("Fetching battle list..."); const gameListFetcher = new BattleListFetcher(
let gameList: string[]; this.opts.listMethod ?? "auto",
if (this.opts.listMethod === "all") { splatnet,
gameList = await splatnet.getAllBattleList(); );
} else {
gameList = await splatnet.getBattleList();
}
const { redraw, endBar } = this.exporterProgress("Export vs games"); const { redraw, endBar } = this.exporterProgress("Export vs games");
const fetcher = new GameFetcher({ const fetcher = new GameFetcher({
@ -189,7 +289,7 @@ export class App {
type: "VsInfo", type: "VsInfo",
fetcher, fetcher,
exporter: e, exporter: e,
gameList, gameListFetcher,
stepProgress: stats[e.name], stepProgress: stats[e.name],
onStep: () => { onStep: () => {
redraw(e.name, progress(stats[e.name])); redraw(e.name, progress(stats[e.name]));
@ -223,10 +323,7 @@ export class App {
if (skipMode.includes("coop") || exporters.length === 0) { if (skipMode.includes("coop") || exporters.length === 0) {
this.env.logger.log("Skip exporting coop games."); this.env.logger.log("Skip exporting coop games.");
} else { } else {
this.env.logger.log("Fetching coop battle list..."); const gameListFetcher = new CoopListFetcher(splatnet);
const coopBattleList = await splatnet.getBattleList(
BattleListType.Coop,
);
const { redraw, endBar } = this.exporterProgress("Export coop games"); const { redraw, endBar } = this.exporterProgress("Export coop games");
const fetcher = new GameFetcher({ const fetcher = new GameFetcher({
@ -243,7 +340,7 @@ export class App {
type: "CoopInfo", type: "CoopInfo",
fetcher, fetcher,
exporter: e, exporter: e,
gameList: coopBattleList, gameListFetcher,
stepProgress: stats[e.name], stepProgress: stats[e.name],
onStep: () => { onStep: () => {
redraw(e.name, progress(stats[e.name])); redraw(e.name, progress(stats[e.name]));
@ -349,30 +446,24 @@ export class App {
* @param gameList ID list of games, sorted by date, newest first * @param gameList ID list of games, sorted by date, newest first
* @param onStep Callback function called when a game is exported * @param onStep Callback function called when a game is exported
*/ */
async exportGameList({ private async exportGameList({
type, type,
fetcher, fetcher,
exporter, exporter,
gameList, gameListFetcher,
stepProgress, stepProgress,
onStep, onStep,
}: { }: {
type: Game["type"]; type: Game["type"];
exporter: GameExporter; exporter: GameExporter;
fetcher: GameFetcher; fetcher: GameFetcher;
gameList: string[]; gameListFetcher: GameListFetcher;
stepProgress: StepProgress; stepProgress: StepProgress;
onStep: () => void; onStep: () => void;
}): Promise<StepProgress> { }): Promise<StepProgress> {
onStep?.(); onStep?.();
const workQueue = [ const workQueue = await gameListFetcher.fetch(exporter);
...await exporter.notExported({
type,
list: gameList,
}),
]
.reverse();
const step = async (id: string) => { const step = async (id: string) => {
const detail = await fetcher.fetch(type, id); const detail = await fetcher.fetch(type, id);

View File

@ -613,7 +613,7 @@ export enum BattleListType {
Coop, Coop,
} }
export type ListMethod = "latest" | "all"; export type ListMethod = "latest" | "all" | "auto";
export type StatInkUuidList = { export type StatInkUuidList = {
status: number; status: number;