From b73769618ddbf9eff0c458a657e0ceeeb570cbb1 Mon Sep 17 00:00:00 2001 From: spacemeowx2 Date: Fri, 21 Oct 2022 02:11:08 +0800 Subject: [PATCH] feat: s3si upload --- .gitignore | 1 + exporter/file.ts | 3 +- exporter/stat.ink.ts | 22 +++++++-- s3si.ts | 113 ++++++++++++++++++++++++++++++++++++------- 4 files changed, 117 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index e9ccc16..ca48f09 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .vscode/ export/ cache/ +.DS_Store diff --git a/exporter/file.ts b/exporter/file.ts index b28c601..2382780 100644 --- a/exporter/file.ts +++ b/exporter/file.ts @@ -1,7 +1,6 @@ import { BattleExporter, VsHistoryDetail } from "../types.ts"; import { datetime, path } from "../deps.ts"; import { NSOAPP_VERSION, S3SI_VERSION } from "../constant.ts"; - const FILENAME_FORMAT = "yyyyMMddHHmmss"; type FileExporterType = { @@ -40,6 +39,8 @@ export class FileExporter implements BattleExporter { await Deno.writeTextFile(filepath, JSON.stringify(body)); } async getLatestBattleTime() { + await Deno.mkdir(this.exportPath, { recursive: true }); + const dirs: Deno.DirEntry[] = []; for await (const i of Deno.readDir(this.exportPath)) dirs.push(i); diff --git a/exporter/stat.ink.ts b/exporter/stat.ink.ts index 7340314..6392f16 100644 --- a/exporter/stat.ink.ts +++ b/exporter/stat.ink.ts @@ -1,5 +1,9 @@ +// deno-lint-ignore-file no-unused-vars require-await +import { USERAGENT } from "../constant.ts"; import { BattleExporter, VsHistoryDetail } from "../types.ts"; +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + /** * Exporter to stat.ink. * @@ -12,10 +16,20 @@ export class StatInkExporter implements BattleExporter { throw new Error("Invalid stat.ink API key"); } } - async exportBattle(detail: VsHistoryDetail) { - throw new Error("Function not implemented."); + requestHeaders() { + return { + "User-Agent": USERAGENT, + "Authorization": `Bearer ${this.statInkApiKey}`, + }; } - async getLatestBattleTime() { - return new Date(); + async exportBattle(detail: VsHistoryDetail) { + await sleep(1000); + } + async getLatestBattleTime(): Promise { + 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"); } } diff --git a/s3si.ts b/s3si.ts index 6dab8b6..c66d515 100644 --- a/s3si.ts +++ b/s3si.ts @@ -144,6 +144,7 @@ Options: const bar = !this.opts.noProgress ? new MultiProgressBar({ title: "Export battles", + display: "[:bar] :text :percent :time eta: :eta :completed/:total", }) : undefined; const exporters = await this.getExporters(); @@ -197,12 +198,14 @@ Options: console.log("Fetching battle list..."); const battleList = await getBattleList(this.state); - const allProgress: Record = Object.fromEntries( - exporters.map((i) => [i.name, { - current: 0, - total: 1, - }]), - ); + await this.prepareBattles({ + bar, + battleList, + fetcher, + exporters, + }); + + const allProgress: Record = {}; const redraw = (name: string, progress: Progress) => { allProgress[name] = progress; bar?.render( @@ -213,6 +216,10 @@ Options: })), ); }; + const stats: Record = Object.fromEntries( + exporters.map((e) => [e.name, 0]), + ); + await Promise.all( exporters.map((e) => this.exportBattleList({ @@ -221,8 +228,16 @@ Options: battleList, 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) { if (e instanceof APIError) { console.error(`APIError: ${e.message}`, e.response, e.json); @@ -231,6 +246,49 @@ Options: } } } + async prepareBattles({ + bar, + exporters, + battleList, + fetcher, + }: { + bar?: MultiProgressBar; + exporters: BattleExporter[]; + 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. * @@ -244,34 +302,55 @@ Options: fetcher, exporter, battleList, - onStep + onStep, }: { - fetcher: BattleFetcher, - exporter: BattleExporter, - battleList: string[], - onStep?: (progress: Progress) => void, + fetcher: BattleFetcher; + exporter: BattleExporter; + battleList: string[]; + onStep?: (progress: Progress) => void; + }, + ): Promise { + 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 detail = await fetcher.fetchBattle(battle); await exporter.exportBattle(detail); - done += 1; + exported += 1; onStep?.({ - current: done, + current: exported, total: workQueue.length, }); }; onStep?.({ - current: done, + current: exported, total: workQueue.length, }); for (const battle of workQueue) { await step(battle); } + + return exported; } }