refactor: move sources to src
parent
d953279549
commit
a63426a282
355
s3si.ts
355
s3si.ts
|
|
@ -1,355 +1,6 @@
|
|||
import { getBulletToken, getGToken, loginManually } from "./iksm.ts";
|
||||
import { flags, MultiProgressBar, Mutex } from "./deps.ts";
|
||||
import { DEFAULT_STATE, State } from "./state.ts";
|
||||
import {
|
||||
checkToken,
|
||||
getBankaraBattleHistories,
|
||||
getBattleDetail,
|
||||
getBattleList,
|
||||
} from "./splatnet3.ts";
|
||||
import {
|
||||
BattleExporter,
|
||||
HistoryGroups,
|
||||
VsBattle,
|
||||
VsHistoryDetail,
|
||||
} from "./types.ts";
|
||||
import { Cache, FileCache, MemoryCache } from "./cache.ts";
|
||||
import { StatInkExporter } from "./exporter/stat.ink.ts";
|
||||
import { battleId, readline, showError } from "./utils.ts";
|
||||
import { FileExporter } from "./exporter/file.ts";
|
||||
|
||||
type Opts = {
|
||||
profilePath: string;
|
||||
exporter: string;
|
||||
noProgress: boolean;
|
||||
help?: boolean;
|
||||
};
|
||||
|
||||
const DEFAULT_OPTS: Opts = {
|
||||
profilePath: "./profile.json",
|
||||
exporter: "stat.ink",
|
||||
noProgress: false,
|
||||
help: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch battle and cache it.
|
||||
*/
|
||||
class BattleFetcher {
|
||||
state: State;
|
||||
cache: Cache;
|
||||
lock: Record<string, Mutex | undefined> = {};
|
||||
bankaraLock = new Mutex();
|
||||
bankaraHistory?: HistoryGroups["nodes"];
|
||||
|
||||
constructor(
|
||||
{ cache = new MemoryCache(), state }: { state: State; cache?: Cache },
|
||||
) {
|
||||
this.state = state;
|
||||
this.cache = cache;
|
||||
}
|
||||
private async getLock(id: string): Promise<Mutex> {
|
||||
const bid = await battleId(id);
|
||||
|
||||
let cur = this.lock[bid];
|
||||
if (!cur) {
|
||||
cur = new Mutex();
|
||||
this.lock[bid] = cur;
|
||||
}
|
||||
|
||||
return cur;
|
||||
}
|
||||
getBankaraHistory() {
|
||||
return this.bankaraLock.use(async () => {
|
||||
if (this.bankaraHistory) {
|
||||
return this.bankaraHistory;
|
||||
}
|
||||
|
||||
const { bankaraBattleHistories: { historyGroups } } =
|
||||
await getBankaraBattleHistories(
|
||||
this.state,
|
||||
);
|
||||
|
||||
this.bankaraHistory = historyGroups.nodes;
|
||||
|
||||
return this.bankaraHistory;
|
||||
});
|
||||
}
|
||||
async getBattleMetaById(id: string): Promise<Omit<VsBattle, "detail">> {
|
||||
const bid = await battleId(id);
|
||||
const bankaraHistory = await this.getBankaraHistory();
|
||||
const group = bankaraHistory.find((i) =>
|
||||
i.historyDetails.nodes.some((i) => i._bid === bid)
|
||||
);
|
||||
|
||||
if (!group) {
|
||||
return {
|
||||
bankaraMatchChallenge: null,
|
||||
listNode: null,
|
||||
};
|
||||
}
|
||||
|
||||
const { bankaraMatchChallenge } = group;
|
||||
const listNode = group.historyDetails.nodes.find((i) => i._bid === bid) ??
|
||||
null;
|
||||
|
||||
return {
|
||||
bankaraMatchChallenge,
|
||||
listNode,
|
||||
};
|
||||
}
|
||||
async getBattleDetail(id: string): Promise<VsHistoryDetail> {
|
||||
const lock = await this.getLock(id);
|
||||
|
||||
return lock.use(async () => {
|
||||
const cached = await this.cache.read<VsHistoryDetail>(id);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const detail = (await getBattleDetail(this.state, id))
|
||||
.vsHistoryDetail;
|
||||
|
||||
await this.cache.write(id, detail);
|
||||
|
||||
return detail;
|
||||
});
|
||||
}
|
||||
async fetchBattle(id: string): Promise<VsBattle> {
|
||||
const detail = await this.getBattleDetail(id);
|
||||
const metadata = await this.getBattleMetaById(id);
|
||||
|
||||
const battle: VsBattle = {
|
||||
...metadata,
|
||||
detail,
|
||||
};
|
||||
|
||||
return battle;
|
||||
}
|
||||
}
|
||||
|
||||
type Progress = {
|
||||
current: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
class App {
|
||||
state: State = DEFAULT_STATE;
|
||||
|
||||
constructor(public opts: Opts) {
|
||||
if (this.opts.help) {
|
||||
console.log(
|
||||
`Usage: deno run -A ${Deno.mainModule} [options]
|
||||
|
||||
Options:
|
||||
--profile-path <path>, -p Path to config file (default: ./profile.json)
|
||||
--exporter <exporter>, -e Exporter list to use (default: stat.ink)
|
||||
Multiple exporters can be separated by commas
|
||||
(e.g. "stat.ink,file")
|
||||
--no-progress, -n Disable progress bar
|
||||
--help Show this help message and exit`,
|
||||
);
|
||||
Deno.exit(0);
|
||||
}
|
||||
}
|
||||
async writeState(newState: State) {
|
||||
this.state = newState;
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(JSON.stringify(this.state, undefined, 2));
|
||||
const swapPath = `${this.opts.profilePath}.swap`;
|
||||
await Deno.writeFile(swapPath, data);
|
||||
await Deno.rename(swapPath, this.opts.profilePath);
|
||||
}
|
||||
async readState() {
|
||||
const decoder = new TextDecoder();
|
||||
try {
|
||||
const data = await Deno.readFile(this.opts.profilePath);
|
||||
const json = JSON.parse(decoder.decode(data));
|
||||
this.state = {
|
||||
...DEFAULT_STATE,
|
||||
...json,
|
||||
};
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
`Failed to read config file, create new config file. (${e})`,
|
||||
);
|
||||
await this.writeState(DEFAULT_STATE);
|
||||
}
|
||||
}
|
||||
async getExporters(): Promise<BattleExporter<VsBattle>[]> {
|
||||
const exporters = this.opts.exporter.split(",");
|
||||
const out: BattleExporter<VsBattle>[] = [];
|
||||
|
||||
if (exporters.includes("stat.ink")) {
|
||||
if (!this.state.statInkApiKey) {
|
||||
console.log("stat.ink API key is not set. Please enter below.");
|
||||
const key = (await readline()).trim();
|
||||
if (!key) {
|
||||
console.error("API key is required.");
|
||||
Deno.exit(1);
|
||||
}
|
||||
await this.writeState({
|
||||
...this.state,
|
||||
statInkApiKey: key,
|
||||
});
|
||||
}
|
||||
out.push(new StatInkExporter(this.state.statInkApiKey!));
|
||||
}
|
||||
|
||||
if (exporters.includes("file")) {
|
||||
out.push(new FileExporter(this.state.fileExportPath));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
async run() {
|
||||
await this.readState();
|
||||
|
||||
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,
|
||||
});
|
||||
console.log("Fetching battle list...");
|
||||
const battleList = await getBattleList(this.state);
|
||||
|
||||
const allProgress: Record<string, Progress> = {};
|
||||
const redraw = (name: string, progress: Progress) => {
|
||||
allProgress[name] = progress;
|
||||
bar?.render(
|
||||
Object.entries(allProgress).map(([name, progress]) => ({
|
||||
completed: progress.current,
|
||||
total: progress.total,
|
||||
text: name,
|
||||
})),
|
||||
);
|
||||
};
|
||||
const stats: Record<string, number> = Object.fromEntries(
|
||||
exporters.map((e) => [e.name, 0]),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
exporters.map((e) =>
|
||||
showError(
|
||||
this.exportBattleList({
|
||||
fetcher,
|
||||
exporter: e,
|
||||
battleList,
|
||||
onStep: (progress) => redraw(e.name, progress),
|
||||
})
|
||||
.then((count) => {
|
||||
stats[e.name] = count;
|
||||
}),
|
||||
)
|
||||
.catch((err) => {
|
||||
console.error(`\nFailed to export to ${e.name}:`, err);
|
||||
})
|
||||
),
|
||||
);
|
||||
|
||||
console.log("\nDone.", stats);
|
||||
}
|
||||
/**
|
||||
* Export battle list.
|
||||
*
|
||||
* @param fetcher BattleFetcher
|
||||
* @param exporter BattleExporter
|
||||
* @param battleList ID list of battles, sorted by date, newest first
|
||||
* @param onStep Callback function called when a battle is exported
|
||||
*/
|
||||
async exportBattleList(
|
||||
{
|
||||
fetcher,
|
||||
exporter,
|
||||
battleList,
|
||||
onStep,
|
||||
}: {
|
||||
fetcher: BattleFetcher;
|
||||
exporter: BattleExporter<VsBattle>;
|
||||
battleList: string[];
|
||||
onStep?: (progress: Progress) => void;
|
||||
},
|
||||
): Promise<number> {
|
||||
let exported = 0;
|
||||
|
||||
onStep?.({
|
||||
current: 0,
|
||||
total: 1,
|
||||
});
|
||||
|
||||
const workQueue = [...await exporter.notExported(battleList)].reverse();
|
||||
|
||||
const step = async (battle: string) => {
|
||||
const detail = await fetcher.fetchBattle(battle);
|
||||
await exporter.exportBattle(detail);
|
||||
exported += 1;
|
||||
onStep?.({
|
||||
current: exported,
|
||||
total: workQueue.length,
|
||||
});
|
||||
};
|
||||
|
||||
if (workQueue.length > 0) {
|
||||
onStep?.({
|
||||
current: exported,
|
||||
total: workQueue.length,
|
||||
});
|
||||
for (const battle of workQueue) {
|
||||
await step(battle);
|
||||
}
|
||||
}
|
||||
|
||||
return exported;
|
||||
}
|
||||
}
|
||||
import { App, DEFAULT_OPTS } from "./src/app.ts";
|
||||
import { showError } from "./src/utils.ts";
|
||||
import { flags } from "./deps.ts";
|
||||
|
||||
const parseArgs = (args: string[]) => {
|
||||
const parsed = flags.parse(args, {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,352 @@
|
|||
import { getBulletToken, getGToken, loginManually } from "./iksm.ts";
|
||||
import { MultiProgressBar, Mutex } from "../deps.ts";
|
||||
import { DEFAULT_STATE, State } from "./state.ts";
|
||||
import {
|
||||
checkToken,
|
||||
getBankaraBattleHistories,
|
||||
getBattleDetail,
|
||||
getBattleList,
|
||||
} from "./splatnet3.ts";
|
||||
import {
|
||||
BattleExporter,
|
||||
HistoryGroups,
|
||||
VsBattle,
|
||||
VsHistoryDetail,
|
||||
} from "./types.ts";
|
||||
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";
|
||||
|
||||
export type Opts = {
|
||||
profilePath: string;
|
||||
exporter: string;
|
||||
noProgress: boolean;
|
||||
help?: boolean;
|
||||
};
|
||||
|
||||
export const DEFAULT_OPTS: Opts = {
|
||||
profilePath: "./profile.json",
|
||||
exporter: "stat.ink",
|
||||
noProgress: false,
|
||||
help: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch battle and cache it.
|
||||
*/
|
||||
class BattleFetcher {
|
||||
state: State;
|
||||
cache: Cache;
|
||||
lock: Record<string, Mutex | undefined> = {};
|
||||
bankaraLock = new Mutex();
|
||||
bankaraHistory?: HistoryGroups["nodes"];
|
||||
|
||||
constructor(
|
||||
{ cache = new MemoryCache(), state }: { state: State; cache?: Cache },
|
||||
) {
|
||||
this.state = state;
|
||||
this.cache = cache;
|
||||
}
|
||||
private async getLock(id: string): Promise<Mutex> {
|
||||
const bid = await battleId(id);
|
||||
|
||||
let cur = this.lock[bid];
|
||||
if (!cur) {
|
||||
cur = new Mutex();
|
||||
this.lock[bid] = cur;
|
||||
}
|
||||
|
||||
return cur;
|
||||
}
|
||||
getBankaraHistory() {
|
||||
return this.bankaraLock.use(async () => {
|
||||
if (this.bankaraHistory) {
|
||||
return this.bankaraHistory;
|
||||
}
|
||||
|
||||
const { bankaraBattleHistories: { historyGroups } } =
|
||||
await getBankaraBattleHistories(
|
||||
this.state,
|
||||
);
|
||||
|
||||
this.bankaraHistory = historyGroups.nodes;
|
||||
|
||||
return this.bankaraHistory;
|
||||
});
|
||||
}
|
||||
async getBattleMetaById(id: string): Promise<Omit<VsBattle, "detail">> {
|
||||
const bid = await battleId(id);
|
||||
const bankaraHistory = await this.getBankaraHistory();
|
||||
const group = bankaraHistory.find((i) =>
|
||||
i.historyDetails.nodes.some((i) => i._bid === bid)
|
||||
);
|
||||
|
||||
if (!group) {
|
||||
return {
|
||||
bankaraMatchChallenge: null,
|
||||
listNode: null,
|
||||
};
|
||||
}
|
||||
|
||||
const { bankaraMatchChallenge } = group;
|
||||
const listNode = group.historyDetails.nodes.find((i) => i._bid === bid) ??
|
||||
null;
|
||||
|
||||
return {
|
||||
bankaraMatchChallenge,
|
||||
listNode,
|
||||
};
|
||||
}
|
||||
async getBattleDetail(id: string): Promise<VsHistoryDetail> {
|
||||
const lock = await this.getLock(id);
|
||||
|
||||
return lock.use(async () => {
|
||||
const cached = await this.cache.read<VsHistoryDetail>(id);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const detail = (await getBattleDetail(this.state, id))
|
||||
.vsHistoryDetail;
|
||||
|
||||
await this.cache.write(id, detail);
|
||||
|
||||
return detail;
|
||||
});
|
||||
}
|
||||
async fetchBattle(id: string): Promise<VsBattle> {
|
||||
const detail = await this.getBattleDetail(id);
|
||||
const metadata = await this.getBattleMetaById(id);
|
||||
|
||||
const battle: VsBattle = {
|
||||
...metadata,
|
||||
detail,
|
||||
};
|
||||
|
||||
return battle;
|
||||
}
|
||||
}
|
||||
|
||||
type Progress = {
|
||||
current: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
export class App {
|
||||
state: State = DEFAULT_STATE;
|
||||
|
||||
constructor(public opts: Opts) {
|
||||
if (this.opts.help) {
|
||||
console.log(
|
||||
`Usage: deno run -A ${Deno.mainModule} [options]
|
||||
|
||||
Options:
|
||||
--profile-path <path>, -p Path to config file (default: ./profile.json)
|
||||
--exporter <exporter>, -e Exporter list to use (default: stat.ink)
|
||||
Multiple exporters can be separated by commas
|
||||
(e.g. "stat.ink,file")
|
||||
--no-progress, -n Disable progress bar
|
||||
--help Show this help message and exit`,
|
||||
);
|
||||
Deno.exit(0);
|
||||
}
|
||||
}
|
||||
async writeState(newState: State) {
|
||||
this.state = newState;
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(JSON.stringify(this.state, undefined, 2));
|
||||
const swapPath = `${this.opts.profilePath}.swap`;
|
||||
await Deno.writeFile(swapPath, data);
|
||||
await Deno.rename(swapPath, this.opts.profilePath);
|
||||
}
|
||||
async readState() {
|
||||
const decoder = new TextDecoder();
|
||||
try {
|
||||
const data = await Deno.readFile(this.opts.profilePath);
|
||||
const json = JSON.parse(decoder.decode(data));
|
||||
this.state = {
|
||||
...DEFAULT_STATE,
|
||||
...json,
|
||||
};
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
`Failed to read config file, create new config file. (${e})`,
|
||||
);
|
||||
await this.writeState(DEFAULT_STATE);
|
||||
}
|
||||
}
|
||||
async getExporters(): Promise<BattleExporter<VsBattle>[]> {
|
||||
const exporters = this.opts.exporter.split(",");
|
||||
const out: BattleExporter<VsBattle>[] = [];
|
||||
|
||||
if (exporters.includes("stat.ink")) {
|
||||
if (!this.state.statInkApiKey) {
|
||||
console.log("stat.ink API key is not set. Please enter below.");
|
||||
const key = (await readline()).trim();
|
||||
if (!key) {
|
||||
console.error("API key is required.");
|
||||
Deno.exit(1);
|
||||
}
|
||||
await this.writeState({
|
||||
...this.state,
|
||||
statInkApiKey: key,
|
||||
});
|
||||
}
|
||||
out.push(new StatInkExporter(this.state.statInkApiKey!));
|
||||
}
|
||||
|
||||
if (exporters.includes("file")) {
|
||||
out.push(new FileExporter(this.state.fileExportPath));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
async run() {
|
||||
await this.readState();
|
||||
|
||||
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,
|
||||
});
|
||||
console.log("Fetching battle list...");
|
||||
const battleList = await getBattleList(this.state);
|
||||
|
||||
const allProgress: Record<string, Progress> = {};
|
||||
const redraw = (name: string, progress: Progress) => {
|
||||
allProgress[name] = progress;
|
||||
bar?.render(
|
||||
Object.entries(allProgress).map(([name, progress]) => ({
|
||||
completed: progress.current,
|
||||
total: progress.total,
|
||||
text: name,
|
||||
})),
|
||||
);
|
||||
};
|
||||
const stats: Record<string, number> = Object.fromEntries(
|
||||
exporters.map((e) => [e.name, 0]),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
exporters.map((e) =>
|
||||
showError(
|
||||
this.exportBattleList({
|
||||
fetcher,
|
||||
exporter: e,
|
||||
battleList,
|
||||
onStep: (progress) => redraw(e.name, progress),
|
||||
})
|
||||
.then((count) => {
|
||||
stats[e.name] = count;
|
||||
}),
|
||||
)
|
||||
.catch((err) => {
|
||||
console.error(`\nFailed to export to ${e.name}:`, err);
|
||||
})
|
||||
),
|
||||
);
|
||||
|
||||
console.log("\nDone.", stats);
|
||||
}
|
||||
/**
|
||||
* Export battle list.
|
||||
*
|
||||
* @param fetcher BattleFetcher
|
||||
* @param exporter BattleExporter
|
||||
* @param battleList ID list of battles, sorted by date, newest first
|
||||
* @param onStep Callback function called when a battle is exported
|
||||
*/
|
||||
async exportBattleList(
|
||||
{
|
||||
fetcher,
|
||||
exporter,
|
||||
battleList,
|
||||
onStep,
|
||||
}: {
|
||||
fetcher: BattleFetcher;
|
||||
exporter: BattleExporter<VsBattle>;
|
||||
battleList: string[];
|
||||
onStep?: (progress: Progress) => void;
|
||||
},
|
||||
): Promise<number> {
|
||||
let exported = 0;
|
||||
|
||||
onStep?.({
|
||||
current: 0,
|
||||
total: 1,
|
||||
});
|
||||
|
||||
const workQueue = [...await exporter.notExported(battleList)].reverse();
|
||||
|
||||
const step = async (battle: string) => {
|
||||
const detail = await fetcher.fetchBattle(battle);
|
||||
await exporter.exportBattle(detail);
|
||||
exported += 1;
|
||||
onStep?.({
|
||||
current: exported,
|
||||
total: workQueue.length,
|
||||
});
|
||||
};
|
||||
|
||||
if (workQueue.length > 0) {
|
||||
onStep?.({
|
||||
current: exported,
|
||||
total: workQueue.length,
|
||||
});
|
||||
for (const battle of workQueue) {
|
||||
await step(battle);
|
||||
}
|
||||
}
|
||||
|
||||
return exported;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// deno-lint-ignore-file require-await
|
||||
import { path } from "./deps.ts";
|
||||
import { path } from "../deps.ts";
|
||||
|
||||
export type Cache = {
|
||||
read: <T>(key: string) => Promise<T | undefined>;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { BattleExporter, VsBattle } from "../types.ts";
|
||||
import { base64, path } from "../deps.ts";
|
||||
import { base64, path } from "../../deps.ts";
|
||||
import { NSOAPP_VERSION, S3SI_VERSION } from "../constant.ts";
|
||||
|
||||
type FileExporterType = {
|
||||
|
|
@ -13,7 +13,7 @@ import {
|
|||
VsHistoryDetail,
|
||||
VsPlayer,
|
||||
} from "../types.ts";
|
||||
import { base64, msgpack } from "../deps.ts";
|
||||
import { base64, msgpack } from "../../deps.ts";
|
||||
import { APIError } from "../APIError.ts";
|
||||
import { battleId, cache } from "../utils.ts";
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { CookieJar, wrapFetch } from "./deps.ts";
|
||||
import { CookieJar, wrapFetch } from "../deps.ts";
|
||||
import { readline, retry, urlBase64Encode } from "./utils.ts";
|
||||
import {
|
||||
DEFAULT_APP_USER_AGENT,
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { APIError } from "./APIError.ts";
|
||||
import { S3SI_NAMESPACE } from "./constant.ts";
|
||||
import { base64, io, uuid } from "./deps.ts";
|
||||
import { base64, io, uuid } from "../deps.ts";
|
||||
|
||||
const stdinLines = io.readLines(Deno.stdin);
|
||||
|
||||
Loading…
Reference in New Issue