feat: s3si upload
parent
f754303a05
commit
b73769618d
|
|
@ -2,3 +2,4 @@
|
||||||
.vscode/
|
.vscode/
|
||||||
export/
|
export/
|
||||||
cache/
|
cache/
|
||||||
|
.DS_Store
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
113
s3si.ts
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue