feat: add udemae info
parent
5737f9da37
commit
74a0ef99ec
|
|
@ -1,4 +1,4 @@
|
||||||
import { BattleExporter, VsHistoryDetail } from "../types.ts";
|
import { BattleExporter, VsBattle } 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";
|
||||||
|
|
@ -8,7 +8,7 @@ type FileExporterType = {
|
||||||
nsoVersion: string;
|
nsoVersion: string;
|
||||||
s3siVersion: string;
|
s3siVersion: string;
|
||||||
exportTime: string;
|
exportTime: string;
|
||||||
data: VsHistoryDetail;
|
data: VsBattle;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -17,14 +17,14 @@ type FileExporterType = {
|
||||||
* This is useful for debugging. It will write each battle detail to a file.
|
* This is useful for debugging. It will write each battle detail to a file.
|
||||||
* Timestamp is used as filename. Example: 2021-01-01T00:00:00.000Z.json
|
* Timestamp is used as filename. Example: 2021-01-01T00:00:00.000Z.json
|
||||||
*/
|
*/
|
||||||
export class FileExporter implements BattleExporter<VsHistoryDetail> {
|
export class FileExporter implements BattleExporter<VsBattle> {
|
||||||
name = "file";
|
name = "file";
|
||||||
constructor(private exportPath: string) {
|
constructor(private exportPath: string) {
|
||||||
}
|
}
|
||||||
async exportBattle(detail: VsHistoryDetail) {
|
async exportBattle(battle: VsBattle) {
|
||||||
await Deno.mkdir(this.exportPath, { recursive: true });
|
await Deno.mkdir(this.exportPath, { recursive: true });
|
||||||
|
|
||||||
const playedTime = new Date(detail.playedTime);
|
const playedTime = new Date(battle.detail.playedTime);
|
||||||
const filename = `${datetime.format(playedTime, FILENAME_FORMAT)}.json`;
|
const filename = `${datetime.format(playedTime, FILENAME_FORMAT)}.json`;
|
||||||
const filepath = path.join(this.exportPath, filename);
|
const filepath = path.join(this.exportPath, filename);
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ export class FileExporter implements BattleExporter<VsHistoryDetail> {
|
||||||
nsoVersion: NSOAPP_VERSION,
|
nsoVersion: NSOAPP_VERSION,
|
||||||
s3siVersion: S3SI_VERSION,
|
s3siVersion: S3SI_VERSION,
|
||||||
exportTime: new Date().toISOString(),
|
exportTime: new Date().toISOString(),
|
||||||
data: detail,
|
data: battle,
|
||||||
};
|
};
|
||||||
|
|
||||||
await Deno.writeTextFile(filepath, JSON.stringify(body));
|
await Deno.writeTextFile(filepath, JSON.stringify(body));
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import {
|
import {
|
||||||
AGENT_NAME,
|
AGENT_NAME,
|
||||||
S3SI_NAMESPACE,
|
|
||||||
S3SI_VERSION,
|
S3SI_VERSION,
|
||||||
SPLATNET3_STATINK_MAP,
|
SPLATNET3_STATINK_MAP,
|
||||||
USERAGENT,
|
USERAGENT,
|
||||||
|
|
@ -10,31 +9,16 @@ import {
|
||||||
StatInkPlayer,
|
StatInkPlayer,
|
||||||
StatInkPostBody,
|
StatInkPostBody,
|
||||||
StatInkStage,
|
StatInkStage,
|
||||||
|
VsBattle,
|
||||||
VsHistoryDetail,
|
VsHistoryDetail,
|
||||||
VsPlayer,
|
VsPlayer,
|
||||||
} from "../types.ts";
|
} from "../types.ts";
|
||||||
import { base64, msgpack, uuid } from "../deps.ts";
|
import { base64, msgpack } from "../deps.ts";
|
||||||
import { APIError } from "../APIError.ts";
|
import { APIError } from "../APIError.ts";
|
||||||
import { cache } from "../utils.ts";
|
import { battleId, cache } from "../utils.ts";
|
||||||
|
|
||||||
const S3S_NAMESPACE = "b3a2dbf5-2c09-4792-b78c-00b548b70aeb";
|
const S3S_NAMESPACE = "b3a2dbf5-2c09-4792-b78c-00b548b70aeb";
|
||||||
|
|
||||||
/**
|
|
||||||
* generate s3s uuid
|
|
||||||
*
|
|
||||||
* @param id ID from SplatNet3
|
|
||||||
* @returns id generated from s3s
|
|
||||||
*/
|
|
||||||
function s3sUuid(id: string): Promise<string> {
|
|
||||||
const fullId = base64.decode(id);
|
|
||||||
const tsUuid = fullId.slice(fullId.length - 52, fullId.length);
|
|
||||||
return uuid.v5.generate(S3S_NAMESPACE, tsUuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
function battleId(id: string): Promise<string> {
|
|
||||||
return uuid.v5.generate(S3SI_NAMESPACE, new TextEncoder().encode(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode ID and get number after '-'
|
* Decode ID and get number after '-'
|
||||||
*/
|
*/
|
||||||
|
|
@ -56,7 +40,7 @@ const getStage = cache(_getStage);
|
||||||
*
|
*
|
||||||
* This is the default exporter. It will upload each battle detail to stat.ink.
|
* This is the default exporter. It will upload each battle detail to stat.ink.
|
||||||
*/
|
*/
|
||||||
export class StatInkExporter implements BattleExporter<VsHistoryDetail> {
|
export class StatInkExporter implements BattleExporter<VsBattle> {
|
||||||
name = "stat.ink";
|
name = "stat.ink";
|
||||||
constructor(private statInkApiKey: string) {
|
constructor(private statInkApiKey: string) {
|
||||||
if (statInkApiKey.length !== 43) {
|
if (statInkApiKey.length !== 43) {
|
||||||
|
|
@ -69,8 +53,8 @@ export class StatInkExporter implements BattleExporter<VsHistoryDetail> {
|
||||||
"Authorization": `Bearer ${this.statInkApiKey}`,
|
"Authorization": `Bearer ${this.statInkApiKey}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
async exportBattle(detail: VsHistoryDetail) {
|
async exportBattle(battle: VsBattle) {
|
||||||
const body = await this.mapBattle(detail);
|
const body = await this.mapBattle(battle);
|
||||||
|
|
||||||
const resp = await fetch("https://stat.ink/api/v3/battle", {
|
const resp = await fetch("https://stat.ink/api/v3/battle", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -100,8 +84,6 @@ export class StatInkExporter implements BattleExporter<VsHistoryDetail> {
|
||||||
json,
|
json,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error("abort");
|
|
||||||
}
|
}
|
||||||
async notExported(list: string[]): Promise<string[]> {
|
async notExported(list: string[]): Promise<string[]> {
|
||||||
const uuid = await (await fetch("https://stat.ink/api/v3/s3s/uuid-list", {
|
const uuid = await (await fetch("https://stat.ink/api/v3/s3s/uuid-list", {
|
||||||
|
|
@ -111,7 +93,7 @@ export class StatInkExporter implements BattleExporter<VsHistoryDetail> {
|
||||||
const out: string[] = [];
|
const out: string[] = [];
|
||||||
|
|
||||||
for (const id of list) {
|
for (const id of list) {
|
||||||
const s3sId = await s3sUuid(id);
|
const s3sId = await battleId(id, S3S_NAMESPACE);
|
||||||
const s3siId = await battleId(id);
|
const s3siId = await battleId(id);
|
||||||
|
|
||||||
if (!uuid.includes(s3sId) && !uuid.includes(s3siId)) {
|
if (!uuid.includes(s3sId) && !uuid.includes(s3siId)) {
|
||||||
|
|
@ -181,10 +163,14 @@ export class StatInkExporter implements BattleExporter<VsHistoryDetail> {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
async mapBattle(vsDetail: VsHistoryDetail): Promise<StatInkPostBody> {
|
async mapBattle(
|
||||||
|
{ lastInChallenge, bankaraMatchChallenge, listNode, detail: vsDetail }:
|
||||||
|
VsBattle,
|
||||||
|
): Promise<StatInkPostBody> {
|
||||||
const {
|
const {
|
||||||
knockout,
|
knockout,
|
||||||
vsMode: { mode },
|
vsMode: { mode },
|
||||||
|
vsRule: { rule },
|
||||||
myTeam,
|
myTeam,
|
||||||
otherTeams,
|
otherTeams,
|
||||||
bankaraMatch,
|
bankaraMatch,
|
||||||
|
|
@ -244,7 +230,7 @@ export class StatInkExporter implements BattleExporter<VsHistoryDetail> {
|
||||||
result.clout_change = festMatch.contribution;
|
result.clout_change = festMatch.contribution;
|
||||||
result.fest_power = festMatch.myFestPower ?? undefined;
|
result.fest_power = festMatch.myFestPower ?? undefined;
|
||||||
}
|
}
|
||||||
if (mode === "FEST" || mode === "REGULAR") {
|
if (rule === "TURF_WAR") {
|
||||||
result.our_team_percent = (myTeam.result.paintRatio ?? 0) * 100;
|
result.our_team_percent = (myTeam.result.paintRatio ?? 0) * 100;
|
||||||
result.their_team_percent = (otherTeams?.[0].result.paintRatio ?? 0) *
|
result.their_team_percent = (otherTeams?.[0].result.paintRatio ?? 0) *
|
||||||
100;
|
100;
|
||||||
|
|
@ -257,17 +243,40 @@ export class StatInkExporter implements BattleExporter<VsHistoryDetail> {
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (mode === "BANKARA") {
|
if (bankaraMatch) {
|
||||||
if (!bankaraMatch) {
|
|
||||||
throw new TypeError("bankaraMatch is null");
|
|
||||||
}
|
|
||||||
result.our_team_count = myTeam.result.score ?? undefined;
|
result.our_team_count = myTeam.result.score ?? undefined;
|
||||||
result.their_team_count = otherTeams?.[0].result.score ?? undefined;
|
result.their_team_count = otherTeams?.[0].result.score ?? undefined;
|
||||||
|
|
||||||
result.knockout = (!knockout || knockout === "NEITHER") ? "no" : "yes";
|
result.knockout = (!knockout || knockout === "NEITHER") ? "no" : "yes";
|
||||||
result.rank_exp_change = bankaraMatch.earnedUdemaePoint;
|
result.rank_exp_change = bankaraMatch.earnedUdemaePoint;
|
||||||
}
|
}
|
||||||
|
if (listNode) {
|
||||||
|
[result.rank_before, result.rank_before_s_plus] = parseUdemae(
|
||||||
|
listNode.udemae,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (bankaraMatchChallenge) {
|
||||||
|
result.rank_up_battle = bankaraMatchChallenge.isPromo ? "yes" : "no";
|
||||||
|
if (bankaraMatchChallenge.udemaeAfter) {
|
||||||
|
[result.rank_after, result.rank_after_s_plus] = parseUdemae(
|
||||||
|
bankaraMatchChallenge.udemaeAfter,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (lastInChallenge) {
|
||||||
|
result.challenge_win = bankaraMatchChallenge.winCount;
|
||||||
|
result.challenge_lose = bankaraMatchChallenge.loseCount;
|
||||||
|
result.rank_exp_change = bankaraMatchChallenge.earnedUdemaePoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseUdemae(udemae: string): [string, number | undefined] {
|
||||||
|
const [rank, rankNum] = udemae.split(/([0-9]+)/);
|
||||||
|
return [
|
||||||
|
rank.toLowerCase(),
|
||||||
|
rankNum === undefined ? undefined : parseInt(rankNum),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
|
||||||
91
s3si.ts
91
s3si.ts
|
|
@ -1,11 +1,21 @@
|
||||||
import { getBulletToken, getGToken, loginManually } from "./iksm.ts";
|
import { getBulletToken, getGToken, loginManually } from "./iksm.ts";
|
||||||
import { flags, MultiProgressBar, Mutex } from "./deps.ts";
|
import { flags, MultiProgressBar, Mutex } from "./deps.ts";
|
||||||
import { DEFAULT_STATE, State } from "./state.ts";
|
import { DEFAULT_STATE, State } from "./state.ts";
|
||||||
import { checkToken, getBattleDetail, getBattleList } from "./splatnet3.ts";
|
import {
|
||||||
import { BattleExporter, VsHistoryDetail } from "./types.ts";
|
checkToken,
|
||||||
|
getBankaraBattleHistories,
|
||||||
|
getBattleDetail,
|
||||||
|
getBattleList,
|
||||||
|
} from "./splatnet3.ts";
|
||||||
|
import {
|
||||||
|
BattleExporter,
|
||||||
|
HistoryGroups,
|
||||||
|
VsBattle,
|
||||||
|
VsHistoryDetail,
|
||||||
|
} from "./types.ts";
|
||||||
import { Cache, FileCache, MemoryCache } from "./cache.ts";
|
import { Cache, FileCache, MemoryCache } from "./cache.ts";
|
||||||
import { StatInkExporter } from "./exporter/stat.ink.ts";
|
import { StatInkExporter } from "./exporter/stat.ink.ts";
|
||||||
import { readline, showError } from "./utils.ts";
|
import { battleId, readline, showError } from "./utils.ts";
|
||||||
import { FileExporter } from "./exporter/file.ts";
|
import { FileExporter } from "./exporter/file.ts";
|
||||||
|
|
||||||
type Opts = {
|
type Opts = {
|
||||||
|
|
@ -29,6 +39,8 @@ class BattleFetcher {
|
||||||
state: State;
|
state: State;
|
||||||
cache: Cache;
|
cache: Cache;
|
||||||
lock: Record<string, Mutex | undefined> = {};
|
lock: Record<string, Mutex | undefined> = {};
|
||||||
|
bankaraLock = new Mutex();
|
||||||
|
bankaraHistory?: HistoryGroups["nodes"];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{ cache = new MemoryCache(), state }: { state: State; cache?: Cache },
|
{ cache = new MemoryCache(), state }: { state: State; cache?: Cache },
|
||||||
|
|
@ -36,16 +48,62 @@ class BattleFetcher {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.cache = cache;
|
this.cache = cache;
|
||||||
}
|
}
|
||||||
getLock(id: string): Mutex {
|
private async getLock(id: string): Promise<Mutex> {
|
||||||
let cur = this.lock[id];
|
const bid = await battleId(id);
|
||||||
|
|
||||||
|
let cur = this.lock[bid];
|
||||||
if (!cur) {
|
if (!cur) {
|
||||||
cur = new Mutex();
|
cur = new Mutex();
|
||||||
this.lock[id] = cur;
|
this.lock[bid] = cur;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cur;
|
return cur;
|
||||||
}
|
}
|
||||||
fetchBattle(id: string): Promise<VsHistoryDetail> {
|
getBankaraHistory() {
|
||||||
const lock = this.getLock(id);
|
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,
|
||||||
|
lastInChallenge: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { bankaraMatchChallenge } = group;
|
||||||
|
const listNode = group.historyDetails.nodes.find((i) => i._bid === bid) ??
|
||||||
|
null;
|
||||||
|
const idx = group.historyDetails.nodes.indexOf(listNode!);
|
||||||
|
|
||||||
|
return {
|
||||||
|
bankaraMatchChallenge,
|
||||||
|
listNode,
|
||||||
|
lastInChallenge: (bankaraMatchChallenge?.state !== "INPROGRESS") &&
|
||||||
|
(idx === 0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async getBattleDetail(id: string): Promise<VsHistoryDetail> {
|
||||||
|
const lock = await this.getLock(id);
|
||||||
|
|
||||||
return lock.use(async () => {
|
return lock.use(async () => {
|
||||||
const cached = await this.cache.read<VsHistoryDetail>(id);
|
const cached = await this.cache.read<VsHistoryDetail>(id);
|
||||||
|
|
@ -61,6 +119,17 @@ class BattleFetcher {
|
||||||
return 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 = {
|
type Progress = {
|
||||||
|
|
@ -111,9 +180,9 @@ Options:
|
||||||
await this.writeState(DEFAULT_STATE);
|
await this.writeState(DEFAULT_STATE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async getExporters(): Promise<BattleExporter<VsHistoryDetail>[]> {
|
async getExporters(): Promise<BattleExporter<VsBattle>[]> {
|
||||||
const exporters = this.opts.exporter.split(",");
|
const exporters = this.opts.exporter.split(",");
|
||||||
const out: BattleExporter<VsHistoryDetail>[] = [];
|
const out: BattleExporter<VsBattle>[] = [];
|
||||||
|
|
||||||
if (exporters.includes("stat.ink")) {
|
if (exporters.includes("stat.ink")) {
|
||||||
if (!this.state.statInkApiKey) {
|
if (!this.state.statInkApiKey) {
|
||||||
|
|
@ -248,7 +317,7 @@ Options:
|
||||||
onStep,
|
onStep,
|
||||||
}: {
|
}: {
|
||||||
fetcher: BattleFetcher;
|
fetcher: BattleFetcher;
|
||||||
exporter: BattleExporter<VsHistoryDetail>;
|
exporter: BattleExporter<VsBattle>;
|
||||||
battleList: string[];
|
battleList: string[];
|
||||||
onStep?: (progress: Progress) => void;
|
onStep?: (progress: Progress) => void;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
11
splatnet3.ts
11
splatnet3.ts
|
|
@ -10,6 +10,7 @@ import {
|
||||||
RespMap,
|
RespMap,
|
||||||
VarsMap,
|
VarsMap,
|
||||||
} from "./types.ts";
|
} from "./types.ts";
|
||||||
|
import { battleId } from "./utils.ts";
|
||||||
|
|
||||||
async function request<Q extends Queries>(
|
async function request<Q extends Queries>(
|
||||||
state: State,
|
state: State,
|
||||||
|
|
@ -121,3 +122,13 @@ export function getBattleDetail(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getBankaraBattleHistories(state: State) {
|
||||||
|
const resp = await request(state, Queries.BankaraBattleHistoriesQuery);
|
||||||
|
for (const i of resp.bankaraBattleHistories.historyGroups.nodes) {
|
||||||
|
for (const j of i.historyDetails.nodes) {
|
||||||
|
j._bid = await battleId(j.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
|
||||||
39
types.ts
39
types.ts
|
|
@ -28,12 +28,28 @@ export type Image = {
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
};
|
};
|
||||||
|
export type BankaraMatchChallenge = {
|
||||||
|
winCount: number;
|
||||||
|
loseCount: number;
|
||||||
|
maxWinCount: number;
|
||||||
|
maxLoseCount: number;
|
||||||
|
state: "FAILED" | "SUCCEEDED" | "INPROGRESS";
|
||||||
|
isPromo: boolean;
|
||||||
|
isUdemaeUp: boolean;
|
||||||
|
udemaeAfter: string | null;
|
||||||
|
earnedUdemaePoint: number;
|
||||||
|
};
|
||||||
|
export type BattleListNode = {
|
||||||
|
// battle id added after fetch
|
||||||
|
_bid: string;
|
||||||
|
id: string;
|
||||||
|
udemae: string;
|
||||||
|
};
|
||||||
export type HistoryGroups = {
|
export type HistoryGroups = {
|
||||||
nodes: {
|
nodes: {
|
||||||
|
bankaraMatchChallenge: null | BankaraMatchChallenge;
|
||||||
historyDetails: {
|
historyDetails: {
|
||||||
nodes: {
|
nodes: BattleListNode[];
|
||||||
id: string;
|
|
||||||
}[];
|
|
||||||
};
|
};
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
@ -65,12 +81,27 @@ export type VsTeam = {
|
||||||
score: null | number;
|
score: null | number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
export type VsRule =
|
||||||
|
| "TURF_WAR"
|
||||||
|
| "AREA"
|
||||||
|
| "LOFT"
|
||||||
|
| "GOAL"
|
||||||
|
| "CLAM"
|
||||||
|
| "TRI_COLOR";
|
||||||
|
|
||||||
|
// With challenge info
|
||||||
|
export type VsBattle = {
|
||||||
|
listNode: null | BattleListNode;
|
||||||
|
bankaraMatchChallenge: null | BankaraMatchChallenge;
|
||||||
|
lastInChallenge: null | boolean;
|
||||||
|
detail: VsHistoryDetail;
|
||||||
|
};
|
||||||
export type VsHistoryDetail = {
|
export type VsHistoryDetail = {
|
||||||
id: string;
|
id: string;
|
||||||
vsRule: {
|
vsRule: {
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
rule: "TURF_WAR" | "AREA" | "LOFT" | "GOAL" | "CLAM" | "TRI_COLOR";
|
rule: VsRule;
|
||||||
};
|
};
|
||||||
vsMode: {
|
vsMode: {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
||||||
12
utils.ts
12
utils.ts
|
|
@ -1,5 +1,6 @@
|
||||||
import { APIError } from "./APIError.ts";
|
import { APIError } from "./APIError.ts";
|
||||||
import { base64, io } from "./deps.ts";
|
import { S3SI_NAMESPACE } from "./constant.ts";
|
||||||
|
import { base64, io, uuid } from "./deps.ts";
|
||||||
|
|
||||||
const stdinLines = io.readLines(Deno.stdin);
|
const stdinLines = io.readLines(Deno.stdin);
|
||||||
|
|
||||||
|
|
@ -80,3 +81,12 @@ export async function showError(p: Promise<void>) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function battleId(
|
||||||
|
id: string,
|
||||||
|
namespace = S3SI_NAMESPACE,
|
||||||
|
): Promise<string> {
|
||||||
|
const fullId = base64.decode(id);
|
||||||
|
const tsUuid = fullId.slice(fullId.length - 52, fullId.length);
|
||||||
|
return uuid.v5.generate(namespace, tsUuid);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue