feat: s3si upload

main
spacemeowx2 2022-10-21 02:11:08 +08:00
parent f754303a05
commit b73769618d
4 changed files with 117 additions and 22 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
.vscode/ .vscode/
export/ export/
cache/ cache/
.DS_Store

View File

@ -1,7 +1,6 @@
import { BattleExporter, VsHistoryDetail } from "../types.ts"; import { BattleExporter, VsHistoryDetail } from "../types.ts";
import { datetime, path } from "../deps.ts"; import { datetime, path } from "../deps.ts";
import { NSOAPP_VERSION, S3SI_VERSION } from "../constant.ts"; import { NSOAPP_VERSION, S3SI_VERSION } from "../constant.ts";
const FILENAME_FORMAT = "yyyyMMddHHmmss"; const FILENAME_FORMAT = "yyyyMMddHHmmss";
type FileExporterType = { type FileExporterType = {
@ -40,6 +39,8 @@ export class FileExporter implements BattleExporter<VsHistoryDetail> {
await Deno.writeTextFile(filepath, JSON.stringify(body)); await Deno.writeTextFile(filepath, JSON.stringify(body));
} }
async getLatestBattleTime() { async getLatestBattleTime() {
await Deno.mkdir(this.exportPath, { recursive: true });
const dirs: Deno.DirEntry[] = []; const dirs: Deno.DirEntry[] = [];
for await (const i of Deno.readDir(this.exportPath)) dirs.push(i); for await (const i of Deno.readDir(this.exportPath)) dirs.push(i);

View File

@ -1,5 +1,9 @@
// deno-lint-ignore-file no-unused-vars require-await
import { USERAGENT } from "../constant.ts";
import { BattleExporter, VsHistoryDetail } from "../types.ts"; import { BattleExporter, VsHistoryDetail } from "../types.ts";
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
/** /**
* Exporter to stat.ink. * Exporter to stat.ink.
* *
@ -12,10 +16,20 @@ export class StatInkExporter implements BattleExporter<VsHistoryDetail> {
throw new Error("Invalid stat.ink API key"); throw new Error("Invalid stat.ink API key");
} }
} }
async exportBattle(detail: VsHistoryDetail) { requestHeaders() {
throw new Error("Function not implemented."); return {
"User-Agent": USERAGENT,
"Authorization": `Bearer ${this.statInkApiKey}`,
};
} }
async getLatestBattleTime() { async exportBattle(detail: VsHistoryDetail) {
return new Date(); await sleep(1000);
}
async getLatestBattleTime(): Promise<Date> {
const uuids = await (await fetch("https://stat.ink/api/v3/s3s/uuid-list", {
headers: this.requestHeaders(),
})).json();
console.log("\n\n uuid:", uuids);
throw new Error("Not implemented");
} }
} }

113
s3si.ts
View File

@ -144,6 +144,7 @@ Options:
const bar = !this.opts.noProgress const bar = !this.opts.noProgress
? new MultiProgressBar({ ? new MultiProgressBar({
title: "Export battles", title: "Export battles",
display: "[:bar] :text :percent :time eta: :eta :completed/:total",
}) })
: undefined; : undefined;
const exporters = await this.getExporters(); const exporters = await this.getExporters();
@ -197,12 +198,14 @@ Options:
console.log("Fetching battle list..."); console.log("Fetching battle list...");
const battleList = await getBattleList(this.state); const battleList = await getBattleList(this.state);
const allProgress: Record<string, Progress> = Object.fromEntries( await this.prepareBattles({
exporters.map((i) => [i.name, { bar,
current: 0, battleList,
total: 1, fetcher,
}]), exporters,
); });
const allProgress: Record<string, Progress> = {};
const redraw = (name: string, progress: Progress) => { const redraw = (name: string, progress: Progress) => {
allProgress[name] = progress; allProgress[name] = progress;
bar?.render( bar?.render(
@ -213,6 +216,10 @@ Options:
})), })),
); );
}; };
const stats: Record<string, number> = Object.fromEntries(
exporters.map((e) => [e.name, 0]),
);
await Promise.all( await Promise.all(
exporters.map((e) => exporters.map((e) =>
this.exportBattleList({ this.exportBattleList({
@ -221,8 +228,16 @@ Options:
battleList, battleList,
onStep: (progress) => redraw(e.name, progress), onStep: (progress) => redraw(e.name, progress),
}) })
.then((count) => {
stats[e.name] = count;
})
.catch((err) => {
console.error(`\nFailed to export ${e.name}:`, err);
})
), ),
); );
console.log("\nDone.", stats);
} catch (e) { } catch (e) {
if (e instanceof APIError) { if (e instanceof APIError) {
console.error(`APIError: ${e.message}`, e.response, e.json); console.error(`APIError: ${e.message}`, e.response, e.json);
@ -231,6 +246,49 @@ Options:
} }
} }
} }
async prepareBattles({
bar,
exporters,
battleList,
fetcher,
}: {
bar?: MultiProgressBar;
exporters: BattleExporter<VsHistoryDetail>[];
battleList: string[];
fetcher: BattleFetcher;
}) {
let prepared = 0;
bar?.render([{
text: "preparing",
completed: prepared,
total: battleList.length,
}]);
const latestBattleTimes = await Promise.all(
exporters.map((e) => e.getLatestBattleTime()),
);
const latestBattleTime = latestBattleTimes.reduce(
(a, b) => a > b ? b : a,
new Date(0),
);
for (const battleId of battleList) {
const battle = await fetcher.fetchBattle(battleId);
const playedTime = new Date(battle.playedTime);
prepared += 1;
bar?.render([{
text: "preparing",
completed: prepared,
total: battleList.length,
}]);
// if battle is older than latest battle, break
if (playedTime <= latestBattleTime) {
break;
}
}
}
/** /**
* Export battle list. * Export battle list.
* *
@ -244,34 +302,55 @@ Options:
fetcher, fetcher,
exporter, exporter,
battleList, battleList,
onStep onStep,
}: { }: {
fetcher: BattleFetcher, fetcher: BattleFetcher;
exporter: BattleExporter<VsHistoryDetail>, exporter: BattleExporter<VsHistoryDetail>;
battleList: string[], battleList: string[];
onStep?: (progress: Progress) => void, onStep?: (progress: Progress) => void;
},
): Promise<number> {
const latestBattleTime = await exporter.getLatestBattleTime();
let toUpload = 0;
let exported = 0;
for (const battleId of battleList) {
const battle = await fetcher.fetchBattle(battleId);
const playedTime = new Date(battle.playedTime);
// if battle is older than latest battle, break
if (playedTime <= latestBattleTime) {
break;
}
toUpload += 1;
}
const workQueue = battleList.slice(0, toUpload).reverse();
if (workQueue.length === 0) {
return 0;
} }
) {
const workQueue = battleList;
let done = 0;
const step = async (battle: string) => { const step = async (battle: string) => {
const detail = await fetcher.fetchBattle(battle); const detail = await fetcher.fetchBattle(battle);
await exporter.exportBattle(detail); await exporter.exportBattle(detail);
done += 1; exported += 1;
onStep?.({ onStep?.({
current: done, current: exported,
total: workQueue.length, total: workQueue.length,
}); });
}; };
onStep?.({ onStep?.({
current: done, current: exported,
total: workQueue.length, total: workQueue.length,
}); });
for (const battle of workQueue) { for (const battle of workQueue) {
await step(battle); await step(battle);
} }
return exported;
} }
} }