refactor: add ExportResult (fix #12)
parent
8d376cfd38
commit
aabc9cf733
109
src/app.ts
109
src/app.ts
|
|
@ -35,6 +35,29 @@ type Progress = {
|
||||||
total: number;
|
total: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class StepProgress {
|
||||||
|
currentUrl?: string;
|
||||||
|
total: number;
|
||||||
|
exported: number;
|
||||||
|
done: number;
|
||||||
|
skipped: Record<string, number>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.total = 1;
|
||||||
|
this.exported = 0;
|
||||||
|
this.done = 0;
|
||||||
|
this.skipped = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function progress({ total, currentUrl, done }: StepProgress): Progress {
|
||||||
|
return {
|
||||||
|
total,
|
||||||
|
currentUrl,
|
||||||
|
current: done,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export class App {
|
export class App {
|
||||||
profile: Profile;
|
profile: Profile;
|
||||||
env: Env;
|
env: Env;
|
||||||
|
|
@ -128,7 +151,7 @@ export class App {
|
||||||
const exporters = await this.getExporters();
|
const exporters = await this.getExporters();
|
||||||
const initStats = () =>
|
const initStats = () =>
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
exporters.map((e) => [e.name, 0]),
|
exporters.map((e) => [e.name, new StepProgress()]),
|
||||||
);
|
);
|
||||||
let stats = initStats();
|
let stats = initStats();
|
||||||
const skipMode = this.getSkipMode();
|
const skipMode = this.getSkipMode();
|
||||||
|
|
@ -158,13 +181,10 @@ export class App {
|
||||||
fetcher,
|
fetcher,
|
||||||
exporter: e,
|
exporter: e,
|
||||||
gameList,
|
gameList,
|
||||||
onStep: (progress) => {
|
stepProgress: stats[e.name],
|
||||||
redraw(e.name, progress);
|
onStep: () => {
|
||||||
stats[e.name] = progress.current;
|
redraw(e.name, progress(stats[e.name]));
|
||||||
},
|
},
|
||||||
})
|
|
||||||
.then((count) => {
|
|
||||||
stats[e.name] = count;
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
|
@ -215,13 +235,10 @@ export class App {
|
||||||
fetcher,
|
fetcher,
|
||||||
exporter: e,
|
exporter: e,
|
||||||
gameList: coopBattleList,
|
gameList: coopBattleList,
|
||||||
onStep: (progress) => {
|
stepProgress: stats[e.name],
|
||||||
stats[e.name] = progress.current;
|
onStep: () => {
|
||||||
redraw(e.name, progress);
|
redraw(e.name, progress(stats[e.name]));
|
||||||
},
|
},
|
||||||
})
|
|
||||||
.then((count) => {
|
|
||||||
stats[e.name] = count;
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
|
@ -295,20 +312,17 @@ export class App {
|
||||||
fetcher,
|
fetcher,
|
||||||
exporter,
|
exporter,
|
||||||
gameList,
|
gameList,
|
||||||
|
stepProgress,
|
||||||
onStep,
|
onStep,
|
||||||
}: {
|
}: {
|
||||||
type: Game["type"];
|
type: Game["type"];
|
||||||
exporter: GameExporter;
|
exporter: GameExporter;
|
||||||
fetcher: GameFetcher;
|
fetcher: GameFetcher;
|
||||||
gameList: string[];
|
gameList: string[];
|
||||||
onStep: (progress: Progress) => void;
|
stepProgress: StepProgress;
|
||||||
}) {
|
onStep: () => void;
|
||||||
let exported = 0;
|
}): Promise<StepProgress> {
|
||||||
|
onStep?.();
|
||||||
onStep?.({
|
|
||||||
current: 0,
|
|
||||||
total: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const workQueue = [
|
const workQueue = [
|
||||||
...await exporter.notExported({
|
...await exporter.notExported({
|
||||||
|
|
@ -320,39 +334,56 @@ export class App {
|
||||||
|
|
||||||
const step = async (id: string) => {
|
const step = async (id: string) => {
|
||||||
const detail = await fetcher.fetch(type, id);
|
const detail = await fetcher.fetch(type, id);
|
||||||
const { url } = await exporter.exportGame(detail);
|
const result = await exporter.exportGame(detail);
|
||||||
exported += 1;
|
|
||||||
onStep?.({
|
stepProgress.done += 1;
|
||||||
currentUrl: url,
|
stepProgress.currentUrl = undefined;
|
||||||
current: exported,
|
|
||||||
total: workQueue.length,
|
if (result.status === "success") {
|
||||||
});
|
stepProgress.exported += 1;
|
||||||
|
stepProgress.currentUrl = result.url;
|
||||||
|
} else if (result.status === "skip") {
|
||||||
|
const { skipped } = stepProgress;
|
||||||
|
skipped[result.reason] = (skipped[result.reason] ?? 0) + 1;
|
||||||
|
} else {
|
||||||
|
const _never: never = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
onStep?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (workQueue.length > 0) {
|
if (workQueue.length > 0) {
|
||||||
onStep?.({
|
stepProgress.total = workQueue.length;
|
||||||
current: exported,
|
onStep?.();
|
||||||
total: workQueue.length,
|
|
||||||
});
|
|
||||||
for (const battle of workQueue) {
|
for (const battle of workQueue) {
|
||||||
await step(battle);
|
await step(battle);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onStep?.({
|
stepProgress.done = 1;
|
||||||
current: 1,
|
onStep?.();
|
||||||
total: 1,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return exported;
|
return stepProgress;
|
||||||
}
|
}
|
||||||
printStats(stats: Record<string, number>) {
|
printStats(stats: Record<string, StepProgress>) {
|
||||||
this.env.logger.log(
|
this.env.logger.log(
|
||||||
`Exported ${
|
`Exported ${
|
||||||
Object.entries(stats)
|
Object.entries(stats)
|
||||||
.map(([name, count]) => `${name}: ${count}`)
|
.map(([name, { exported }]) => `${name}: ${exported}`)
|
||||||
.join(", ")
|
.join(", ")
|
||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
|
if (Object.values(stats).some((i) => Object.keys(i.skipped).length > 0)) {
|
||||||
|
this.env.logger.log(
|
||||||
|
`Skipped ${
|
||||||
|
Object.entries(stats)
|
||||||
|
.map(([name, { skipped }]) =>
|
||||||
|
Object.entries(skipped).map(([reason, count]) =>
|
||||||
|
`${name}: ${reason} (${count})`
|
||||||
|
).join(", ")
|
||||||
|
)
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
import { CoopInfo, Game, GameExporter, VsInfo } from "../types.ts";
|
import {
|
||||||
|
CoopInfo,
|
||||||
|
ExportResult,
|
||||||
|
Game,
|
||||||
|
GameExporter,
|
||||||
|
VsInfo,
|
||||||
|
} from "../types.ts";
|
||||||
import { path } from "../../deps.ts";
|
import { path } from "../../deps.ts";
|
||||||
import { NSOAPP_VERSION, S3SI_VERSION } from "../constant.ts";
|
import { NSOAPP_VERSION, S3SI_VERSION } from "../constant.ts";
|
||||||
import { parseHistoryDetailId, urlSimplify } from "../utils.ts";
|
import { parseHistoryDetailId, urlSimplify } from "../utils.ts";
|
||||||
|
|
@ -83,7 +89,7 @@ export class FileExporter implements GameExporter {
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
async exportGame(info: Game) {
|
async exportGame(info: Game): Promise<ExportResult> {
|
||||||
await Deno.mkdir(this.exportPath, { recursive: true });
|
await Deno.mkdir(this.exportPath, { recursive: true });
|
||||||
|
|
||||||
const filename = this.getFilenameById(info.detail.id);
|
const filename = this.getFilenameById(info.detail.id);
|
||||||
|
|
@ -103,6 +109,7 @@ export class FileExporter implements GameExporter {
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
status: "success",
|
||||||
url: filepath,
|
url: filepath,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import {
|
||||||
CoopHistoryDetail,
|
CoopHistoryDetail,
|
||||||
CoopHistoryPlayerResult,
|
CoopHistoryPlayerResult,
|
||||||
CoopInfo,
|
CoopInfo,
|
||||||
|
ExportResult,
|
||||||
Game,
|
Game,
|
||||||
GameExporter,
|
GameExporter,
|
||||||
Image,
|
Image,
|
||||||
|
|
@ -231,10 +232,13 @@ export class StatInkExporter implements GameExporter {
|
||||||
isTriColor({ vsMode }: VsHistoryDetail): boolean {
|
isTriColor({ vsMode }: VsHistoryDetail): boolean {
|
||||||
return vsMode.mode === "FEST" && b64Number(vsMode.id) === 8;
|
return vsMode.mode === "FEST" && b64Number(vsMode.id) === 8;
|
||||||
}
|
}
|
||||||
async exportGame(game: VsInfo | CoopInfo) {
|
async exportGame(game: Game): Promise<ExportResult> {
|
||||||
if (game.type === "VsInfo" && this.isTriColor(game.detail)) {
|
if (game.type === "VsInfo" && this.isTriColor(game.detail)) {
|
||||||
// TODO: support tri-color fest
|
// TODO: support tri-color fest
|
||||||
return {};
|
return {
|
||||||
|
status: "skip",
|
||||||
|
reason: "Tri-color fest is not supported",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game.type === "VsInfo") {
|
if (game.type === "VsInfo") {
|
||||||
|
|
@ -242,6 +246,7 @@ export class StatInkExporter implements GameExporter {
|
||||||
const { url } = await this.api.postBattle(body);
|
const { url } = await this.api.postBattle(body);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
status: "success",
|
||||||
url,
|
url,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -249,6 +254,7 @@ export class StatInkExporter implements GameExporter {
|
||||||
const { url } = await this.api.postCoop(body);
|
const { url } = await this.api.postCoop(body);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
status: "success",
|
||||||
url,
|
url,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
23
src/types.ts
23
src/types.ts
|
|
@ -296,12 +296,27 @@ export type CoopHistoryDetail = {
|
||||||
jobBonus: null | number;
|
jobBonus: null | number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ExportResult = {
|
||||||
|
status: "success";
|
||||||
|
url?: string;
|
||||||
|
} | {
|
||||||
|
status: "skip";
|
||||||
|
reason: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SummaryFetcher = {
|
||||||
|
fetchSummary<T extends (typeof Queries)[keyof typeof SummaryEnum]>(
|
||||||
|
type: T,
|
||||||
|
): Promise<RespMap[T]>;
|
||||||
|
};
|
||||||
|
|
||||||
export type GameExporter = {
|
export type GameExporter = {
|
||||||
name: string;
|
name: string;
|
||||||
notExported: (
|
notExported: (
|
||||||
{ type, list }: { type: Game["type"]; list: string[] },
|
{ type, list }: { type: Game["type"]; list: string[] },
|
||||||
) => Promise<string[]>;
|
) => Promise<string[]>;
|
||||||
exportGame: (game: Game) => Promise<{ url?: string }>;
|
exportGame: (game: Game) => Promise<ExportResult>;
|
||||||
|
exportSummary?: (fetcher: SummaryFetcher) => Promise<ExportResult>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BankaraBattleHistories = {
|
export type BankaraBattleHistories = {
|
||||||
|
|
@ -737,7 +752,7 @@ export type RankParam = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum SummaryEnum {
|
export enum SummaryEnum {
|
||||||
ConfigureAnalyticsQuery,
|
ConfigureAnalyticsQuery = Queries.ConfigureAnalyticsQuery,
|
||||||
HistoryRecordQuery,
|
HistoryRecordQuery = Queries.HistoryRecordQuery,
|
||||||
CoopHistoryQuery,
|
CoopHistoryQuery = Queries.CoopHistoryQuery,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue