diff --git a/s3si.ts b/s3si.ts index d9897c9..12d2cf7 100644 --- a/s3si.ts +++ b/s3si.ts @@ -5,12 +5,13 @@ import { flags } from "./deps.ts"; const parseArgs = (args: string[]) => { const parsed = flags.parse(args, { string: ["profilePath", "exporter"], - boolean: ["help", "noProgress"], + boolean: ["help", "noProgress", "monitor"], alias: { "help": "h", "profilePath": ["p", "profile-path"], "exporter": ["e"], "noProgress": ["n", "no-progress"], + "monitor": ["m"], }, }); return parsed; diff --git a/src/app.ts b/src/app.ts index 65a719f..3b06bab 100644 --- a/src/app.ts +++ b/src/app.ts @@ -17,7 +17,7 @@ import { import { Cache, FileCache, MemoryCache } from "./cache.ts"; import { StatInkExporter } from "./exporters/stat.ink.ts"; import { FileExporter } from "./exporters/file.ts"; -import { battleId, readline, showError } from "./utils.ts"; +import { battleId, delay, readline, showError } from "./utils.ts"; export type Opts = { profilePath: string; @@ -200,7 +200,12 @@ export class App { statInkApiKey: key, }); } - out.push(new StatInkExporter(this.state.statInkApiKey!)); + out.push( + new StatInkExporter( + this.state.statInkApiKey!, + this.opts.monitor ? "Monitoring" : "Manual", + ), + ); } if (exporters.includes("file")) { @@ -209,58 +214,16 @@ export class App { return out; } - async run() { - await this.readState(); - + async exportOnce() { 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(); - if (!this.state.loginState?.sessionToken) { - const sessionToken = await loginManually(); - - await this.writeState({ - ...this.state, - loginState: { - ...this.state.loginState, - sessionToken, - }, - }); - } - const sessionToken = this.state.loginState!.sessionToken!; - - console.log("Checking token..."); - if (!await checkToken(this.state)) { - console.log("Token expired, refetch tokens."); - - const { webServiceToken, userCountry, userLang } = await getGToken({ - fApi: this.state.fGen, - sessionToken, - }); - - const bulletToken = await getBulletToken({ - webServiceToken, - userLang, - userCountry, - appUserAgent: this.state.appUserAgent, - }); - - await this.writeState({ - ...this.state, - loginState: { - ...this.state.loginState, - gToken: webServiceToken, - bulletToken, - }, - userLang: this.state.userLang ?? userLang, - userCountry: this.state.userCountry ?? userCountry, - }); - } - const fetcher = new BattleFetcher({ cache: new FileCache(this.state.cacheDir), state: this.state, @@ -302,7 +265,87 @@ export class App { ), ); - console.log("\nDone.", stats); + bar?.end(); + + console.log( + `Exported ${ + Object.entries(stats) + .map(([name, count]) => `${name}: ${count}`) + .join(", ") + }`, + ); + } + async monitor() { + while (true) { + await this.exportOnce(); + await this.countDown(this.state.monitorInterval); + } + } + async countDown(sec: number) { + const bar = !this.opts.noProgress + ? new MultiProgressBar({ + title: "Killing time...", + display: "[:bar] :completed/:total", + }) + : undefined; + for (const i of Array(sec).keys()) { + bar?.render([{ + completed: i, + total: sec, + }]); + await delay(1000); + } + bar?.end(); + } + async run() { + await this.readState(); + + if (!this.state.loginState?.sessionToken) { + const sessionToken = await loginManually(); + + await this.writeState({ + ...this.state, + loginState: { + ...this.state.loginState, + sessionToken, + }, + }); + } + const sessionToken = this.state.loginState!.sessionToken!; + + console.log("Checking token..."); + if (!await checkToken(this.state)) { + console.log("Token expired, refetch tokens."); + + const { webServiceToken, userCountry, userLang } = await getGToken({ + fApi: this.state.fGen, + sessionToken, + }); + + const bulletToken = await getBulletToken({ + webServiceToken, + userLang, + userCountry, + appUserAgent: this.state.appUserAgent, + }); + + await this.writeState({ + ...this.state, + loginState: { + ...this.state.loginState, + gToken: webServiceToken, + bulletToken, + }, + userLang: this.state.userLang ?? userLang, + userCountry: this.state.userCountry ?? userCountry, + }); + } + + if (this.opts.monitor) { + await this.monitor(); + } else { + await this.exportOnce(); + } } /** * Export battle list. diff --git a/src/constant.ts b/src/constant.ts index 7fc0ccf..be352ec 100644 --- a/src/constant.ts +++ b/src/constant.ts @@ -1,7 +1,7 @@ import type { StatInkPostBody, VsHistoryDetail } from "./types.ts"; export const AGENT_NAME = "s3si.ts"; -export const S3SI_VERSION = "0.1.5"; +export const S3SI_VERSION = "0.1.6"; export const NSOAPP_VERSION = "2.3.1"; export const WEB_VIEW_VERSION = "1.0.0-216d0219"; diff --git a/src/exporters/stat.ink.ts b/src/exporters/stat.ink.ts index 6ddccdd..64db4d7 100644 --- a/src/exporters/stat.ink.ts +++ b/src/exporters/stat.ink.ts @@ -42,7 +42,8 @@ const getStage = cache(_getStage); */ export class StatInkExporter implements BattleExporter { name = "stat.ink"; - constructor(private statInkApiKey: string) { + + constructor(private statInkApiKey: string, private uploadMode: string) { if (statInkApiKey.length !== 43) { throw new Error("Invalid stat.ink API key"); } @@ -203,7 +204,9 @@ export class StatInkExporter implements BattleExporter { agent: AGENT_NAME, agent_version: S3SI_VERSION, - agent_variables: undefined, + agent_variables: { + "Upload Mode": this.uploadMode, + }, automated: "yes", start_at: startedAt, end_at: startedAt + vsDetail.duration, diff --git a/src/state.ts b/src/state.ts index 90570b7..414e33b 100644 --- a/src/state.ts +++ b/src/state.ts @@ -15,10 +15,12 @@ export type State = { // Exporter config statInkApiKey?: string; fileExportPath: string; + monitorInterval: number; }; export const DEFAULT_STATE: State = { cacheDir: "./cache", fGen: "https://api.imink.app/f", fileExportPath: "./export", + monitorInterval: 500, }; diff --git a/src/utils.ts b/src/utils.ts index ae0eff1..0bbd6ed 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -113,3 +113,6 @@ export function parseVsHistoryDetailId(id: string) { uuid, }; } + +export const delay = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms));