commit
35b47d79c2
|
|
@ -1,3 +1,7 @@
|
||||||
|
## 0.1.17
|
||||||
|
|
||||||
|
feat: add gears to stat.ink
|
||||||
|
|
||||||
## 0.1.16
|
## 0.1.16
|
||||||
|
|
||||||
fix: RankTracker broken when token expires
|
fix: RankTracker broken when token expires
|
||||||
|
|
|
||||||
32
src/app.ts
32
src/app.ts
|
|
@ -6,7 +6,7 @@ import {
|
||||||
State,
|
State,
|
||||||
StateBackend,
|
StateBackend,
|
||||||
} from "./state.ts";
|
} from "./state.ts";
|
||||||
import { getBattleList, isTokenExpired } from "./splatnet3.ts";
|
import { getBattleList, getGearPower, isTokenExpired } from "./splatnet3.ts";
|
||||||
import { BattleListType, Game, GameExporter } from "./types.ts";
|
import { BattleListType, Game, GameExporter } from "./types.ts";
|
||||||
import { Cache, FileCache } from "./cache.ts";
|
import { Cache, FileCache } from "./cache.ts";
|
||||||
import { StatInkExporter } from "./exporters/stat.ink.ts";
|
import { StatInkExporter } from "./exporters/stat.ink.ts";
|
||||||
|
|
@ -65,6 +65,7 @@ export class App {
|
||||||
await this.fetchToken();
|
await this.fetchToken();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
gearMap: Record<string, number> | null = null;
|
||||||
|
|
||||||
constructor(public opts: Opts) {
|
constructor(public opts: Opts) {
|
||||||
this.stateBackend = opts.stateBackend ??
|
this.stateBackend = opts.stateBackend ??
|
||||||
|
|
@ -88,6 +89,16 @@ export class App {
|
||||||
await this.writeState(DEFAULT_STATE);
|
await this.writeState(DEFAULT_STATE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async getGearMap() {
|
||||||
|
if (this.gearMap) {
|
||||||
|
return this.gearMap;
|
||||||
|
}
|
||||||
|
const { gearPowers } = await getGearPower(this.state);
|
||||||
|
this.gearMap = Object.fromEntries(
|
||||||
|
gearPowers.nodes.map((i, id) => [i.name, id]),
|
||||||
|
);
|
||||||
|
return this.gearMap;
|
||||||
|
}
|
||||||
getSkipMode(): ("vs" | "coop")[] {
|
getSkipMode(): ("vs" | "coop")[] {
|
||||||
const mode = this.opts.skipMode;
|
const mode = this.opts.skipMode;
|
||||||
if (mode === "vs") {
|
if (mode === "vs") {
|
||||||
|
|
@ -115,10 +126,13 @@ export class App {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
out.push(
|
out.push(
|
||||||
new StatInkExporter(
|
new StatInkExporter({
|
||||||
this.state.statInkApiKey!,
|
statInkApiKey: this.state.statInkApiKey!,
|
||||||
this.opts.monitor ? "Monitoring" : "Manual",
|
uploadMode: this.opts.monitor ? "Monitoring" : "Manual",
|
||||||
),
|
nameDict: {
|
||||||
|
gearPower: await this.getGearMap(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,6 +224,8 @@ export class App {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
endBar();
|
||||||
|
|
||||||
printStats(stats);
|
printStats(stats);
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
throw errors[0];
|
throw errors[0];
|
||||||
|
|
@ -221,8 +237,6 @@ export class App {
|
||||||
...this.state,
|
...this.state,
|
||||||
rankState: finalRankState,
|
rankState: finalRankState,
|
||||||
});
|
});
|
||||||
|
|
||||||
endBar();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stats = initStats();
|
stats = initStats();
|
||||||
|
|
@ -268,12 +282,12 @@ export class App {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
endBar();
|
||||||
|
|
||||||
printStats(stats);
|
printStats(stats);
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
throw errors[0];
|
throw errors[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
endBar();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async monitor() {
|
async monitor() {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type { StatInkPostBody, VsHistoryDetail } from "./types.ts";
|
import type { StatInkPostBody, VsHistoryDetail } from "./types.ts";
|
||||||
|
|
||||||
export const AGENT_NAME = "s3si.ts";
|
export const AGENT_NAME = "s3si.ts";
|
||||||
export const S3SI_VERSION = "0.1.16";
|
export const S3SI_VERSION = "0.1.17";
|
||||||
export const NSOAPP_VERSION = "2.3.1";
|
export const NSOAPP_VERSION = "2.3.1";
|
||||||
export const WEB_VIEW_VERSION = "1.0.0-5644e7a2";
|
export const WEB_VIEW_VERSION = "1.0.0-5644e7a2";
|
||||||
export const S3SI_LINK = "https://github.com/spacemeowx2/s3si.ts";
|
export const S3SI_LINK = "https://github.com/spacemeowx2/s3si.ts";
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,10 @@ import {
|
||||||
import {
|
import {
|
||||||
CoopInfo,
|
CoopInfo,
|
||||||
GameExporter,
|
GameExporter,
|
||||||
|
PlayerGear,
|
||||||
|
StatInkAbility,
|
||||||
|
StatInkGear,
|
||||||
|
StatInkGears,
|
||||||
StatInkPlayer,
|
StatInkPlayer,
|
||||||
StatInkPostBody,
|
StatInkPostBody,
|
||||||
StatInkPostResponse,
|
StatInkPostResponse,
|
||||||
|
|
@ -17,7 +21,7 @@ import {
|
||||||
VsInfo,
|
VsInfo,
|
||||||
VsPlayer,
|
VsPlayer,
|
||||||
} from "../types.ts";
|
} from "../types.ts";
|
||||||
import { base64, msgpack } from "../../deps.ts";
|
import { base64, msgpack, Mutex } from "../../deps.ts";
|
||||||
import { APIError } from "../APIError.ts";
|
import { APIError } from "../APIError.ts";
|
||||||
import { cache, gameId } from "../utils.ts";
|
import { cache, gameId } from "../utils.ts";
|
||||||
|
|
||||||
|
|
@ -30,13 +34,29 @@ function b64Number(id: string): number {
|
||||||
return parseInt(num);
|
return parseInt(num);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FETCH_LOCK = new Mutex();
|
||||||
|
async function _getAbility(): Promise<StatInkAbility> {
|
||||||
|
const release = await FETCH_LOCK.acquire();
|
||||||
|
try {
|
||||||
|
const resp = await fetch("https://stat.ink/api/v3/ability?full=1");
|
||||||
|
const json = await resp.json();
|
||||||
|
return json;
|
||||||
|
} finally {
|
||||||
|
release();
|
||||||
|
}
|
||||||
|
}
|
||||||
async function _getStage(): Promise<StatInkStage> {
|
async function _getStage(): Promise<StatInkStage> {
|
||||||
const resp = await fetch("https://stat.ink/api/v3/stage");
|
const resp = await fetch("https://stat.ink/api/v3/stage");
|
||||||
const json = await resp.json();
|
const json = await resp.json();
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
const getAbility = cache(_getAbility);
|
||||||
const getStage = cache(_getStage);
|
const getStage = cache(_getStage);
|
||||||
|
|
||||||
|
export type NameDict = {
|
||||||
|
gearPower: Record<string, number | undefined>;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exporter to stat.ink.
|
* Exporter to stat.ink.
|
||||||
*
|
*
|
||||||
|
|
@ -44,11 +64,23 @@ const getStage = cache(_getStage);
|
||||||
*/
|
*/
|
||||||
export class StatInkExporter implements GameExporter {
|
export class StatInkExporter implements GameExporter {
|
||||||
name = "stat.ink";
|
name = "stat.ink";
|
||||||
|
private statInkApiKey: string;
|
||||||
|
private uploadMode: string;
|
||||||
|
private nameDict: NameDict;
|
||||||
|
|
||||||
constructor(private statInkApiKey: string, private uploadMode: string) {
|
constructor(
|
||||||
|
{ statInkApiKey, uploadMode, nameDict }: {
|
||||||
|
statInkApiKey: string;
|
||||||
|
uploadMode: string;
|
||||||
|
nameDict: NameDict;
|
||||||
|
},
|
||||||
|
) {
|
||||||
if (statInkApiKey.length !== 43) {
|
if (statInkApiKey.length !== 43) {
|
||||||
throw new Error("Invalid stat.ink API key");
|
throw new Error("Invalid stat.ink API key");
|
||||||
}
|
}
|
||||||
|
this.statInkApiKey = statInkApiKey;
|
||||||
|
this.uploadMode = uploadMode;
|
||||||
|
this.nameDict = nameDict;
|
||||||
}
|
}
|
||||||
requestHeaders() {
|
requestHeaders() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -157,7 +189,43 @@ export class StatInkExporter implements GameExporter {
|
||||||
|
|
||||||
return result.key;
|
return result.key;
|
||||||
}
|
}
|
||||||
mapPlayer(player: VsPlayer, index: number): StatInkPlayer {
|
async mapGears(
|
||||||
|
{ headGear, clothingGear, shoesGear }: VsPlayer,
|
||||||
|
): Promise<StatInkGears> {
|
||||||
|
const amap = await getAbility();
|
||||||
|
const mapAbility = ({ name }: { name: string }): string | null => {
|
||||||
|
const abilityIdx = this.nameDict.gearPower[name];
|
||||||
|
if (!abilityIdx) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const result = amap[abilityIdx];
|
||||||
|
if (!result) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return result.key;
|
||||||
|
};
|
||||||
|
const mapGear = (
|
||||||
|
{ primaryGearPower, additionalGearPowers }: PlayerGear,
|
||||||
|
): StatInkGear => {
|
||||||
|
const primary = mapAbility(primaryGearPower);
|
||||||
|
if (!primary) {
|
||||||
|
throw new Error("Unknown ability: " + primaryGearPower.name);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
primary_ability: primary,
|
||||||
|
secondary_abilities: additionalGearPowers.map(mapAbility),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
headgear: mapGear(headGear),
|
||||||
|
clothing: mapGear(clothingGear),
|
||||||
|
shoes: mapGear(shoesGear),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
mapPlayer = async (
|
||||||
|
player: VsPlayer,
|
||||||
|
index: number,
|
||||||
|
): Promise<StatInkPlayer> => {
|
||||||
const result: StatInkPlayer = {
|
const result: StatInkPlayer = {
|
||||||
me: player.isMyself ? "yes" : "no",
|
me: player.isMyself ? "yes" : "no",
|
||||||
rank_in_team: index + 1,
|
rank_in_team: index + 1,
|
||||||
|
|
@ -166,6 +234,7 @@ export class StatInkExporter implements GameExporter {
|
||||||
splashtag_title: player.byname,
|
splashtag_title: player.byname,
|
||||||
weapon: b64Number(player.weapon.id).toString(),
|
weapon: b64Number(player.weapon.id).toString(),
|
||||||
inked: player.paint,
|
inked: player.paint,
|
||||||
|
gears: await this.mapGears(player),
|
||||||
disconnected: player.result ? "no" : "yes",
|
disconnected: player.result ? "no" : "yes",
|
||||||
};
|
};
|
||||||
if (player.result) {
|
if (player.result) {
|
||||||
|
|
@ -176,7 +245,7 @@ export class StatInkExporter implements GameExporter {
|
||||||
result.special = player.result.special;
|
result.special = player.result.special;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
};
|
||||||
async mapBattle(
|
async mapBattle(
|
||||||
{
|
{
|
||||||
challengeProgress,
|
challengeProgress,
|
||||||
|
|
@ -216,9 +285,11 @@ export class StatInkExporter implements GameExporter {
|
||||||
|
|
||||||
medals: vsDetail.awards.map((i) => i.name),
|
medals: vsDetail.awards.map((i) => i.name),
|
||||||
|
|
||||||
our_team_players: myTeam.players.map(this.mapPlayer),
|
our_team_players: await Promise.all(myTeam.players.map(this.mapPlayer)),
|
||||||
their_team_players: otherTeams.flatMap((i) => i.players).map(
|
their_team_players: await Promise.all(
|
||||||
this.mapPlayer,
|
otherTeams.flatMap((i) => i.players).map(
|
||||||
|
this.mapPlayer,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
agent: AGENT_NAME,
|
agent: AGENT_NAME,
|
||||||
|
|
|
||||||
|
|
@ -162,3 +162,12 @@ export async function getCoopHistories(state: State) {
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getGearPower(state: State) {
|
||||||
|
const resp = await request(
|
||||||
|
state,
|
||||||
|
Queries.myOutfitCommonDataFilteringConditionQuery,
|
||||||
|
);
|
||||||
|
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
|
||||||
45
src/types.ts
45
src/types.ts
|
|
@ -9,6 +9,8 @@ export enum Queries {
|
||||||
VsHistoryDetailQuery = "2b085984f729cd51938fc069ceef784a",
|
VsHistoryDetailQuery = "2b085984f729cd51938fc069ceef784a",
|
||||||
CoopHistoryQuery = "817618ce39bcf5570f52a97d73301b30",
|
CoopHistoryQuery = "817618ce39bcf5570f52a97d73301b30",
|
||||||
CoopHistoryDetailQuery = "f3799a033f0a7ad4b1b396f9a3bafb1e",
|
CoopHistoryDetailQuery = "f3799a033f0a7ad4b1b396f9a3bafb1e",
|
||||||
|
myOutfitCommonDataFilteringConditionQuery =
|
||||||
|
"d02ab22c9dccc440076055c8baa0fa7a",
|
||||||
}
|
}
|
||||||
export type VarsMap = {
|
export type VarsMap = {
|
||||||
[Queries.HomeQuery]: [];
|
[Queries.HomeQuery]: [];
|
||||||
|
|
@ -23,6 +25,7 @@ export type VarsMap = {
|
||||||
[Queries.CoopHistoryDetailQuery]: [{
|
[Queries.CoopHistoryDetailQuery]: [{
|
||||||
coopHistoryDetailId: string;
|
coopHistoryDetailId: string;
|
||||||
}];
|
}];
|
||||||
|
[Queries.myOutfitCommonDataFilteringConditionQuery]: [];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Image = {
|
export type Image = {
|
||||||
|
|
@ -61,6 +64,19 @@ export type HistoryGroups<T> = {
|
||||||
};
|
};
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
export type PlayerGear = {
|
||||||
|
name: string;
|
||||||
|
primaryGearPower: {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
additionalGearPowers: {
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
|
brand: {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
export type VsPlayer = {
|
export type VsPlayer = {
|
||||||
id: string;
|
id: string;
|
||||||
nameId: string | null;
|
nameId: string | null;
|
||||||
|
|
@ -81,6 +97,10 @@ export type VsPlayer = {
|
||||||
special: number;
|
special: number;
|
||||||
} | null;
|
} | null;
|
||||||
paint: number;
|
paint: number;
|
||||||
|
|
||||||
|
headGear: PlayerGear;
|
||||||
|
clothingGear: PlayerGear;
|
||||||
|
shoesGear: PlayerGear;
|
||||||
};
|
};
|
||||||
export type VsTeam = {
|
export type VsTeam = {
|
||||||
players: VsPlayer[];
|
players: VsPlayer[];
|
||||||
|
|
@ -223,6 +243,13 @@ export type RespMap = {
|
||||||
[Queries.CoopHistoryDetailQuery]: {
|
[Queries.CoopHistoryDetailQuery]: {
|
||||||
coopHistoryDetail: CoopHistoryDetail;
|
coopHistoryDetail: CoopHistoryDetail;
|
||||||
};
|
};
|
||||||
|
[Queries.myOutfitCommonDataFilteringConditionQuery]: {
|
||||||
|
gearPowers: {
|
||||||
|
nodes: {
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
export type GraphQLResponse<T> = {
|
export type GraphQLResponse<T> = {
|
||||||
data: T;
|
data: T;
|
||||||
|
|
@ -240,6 +267,23 @@ export enum BattleListType {
|
||||||
Coop,
|
Coop,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type StatInkAbility = {
|
||||||
|
key: string;
|
||||||
|
name: Record<string, string>;
|
||||||
|
primary_only: boolean;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
export type StatInkGear = {
|
||||||
|
primary_ability: string;
|
||||||
|
secondary_abilities: (string | null)[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StatInkGears = {
|
||||||
|
headgear: StatInkGear;
|
||||||
|
clothing: StatInkGear;
|
||||||
|
shoes: StatInkGear;
|
||||||
|
};
|
||||||
|
|
||||||
export type StatInkPlayer = {
|
export type StatInkPlayer = {
|
||||||
me: "yes" | "no";
|
me: "yes" | "no";
|
||||||
rank_in_team: number;
|
rank_in_team: number;
|
||||||
|
|
@ -253,6 +297,7 @@ export type StatInkPlayer = {
|
||||||
kill_or_assist?: number;
|
kill_or_assist?: number;
|
||||||
death?: number;
|
death?: number;
|
||||||
special?: number;
|
special?: number;
|
||||||
|
gears?: StatInkGears;
|
||||||
disconnected: "yes" | "no";
|
disconnected: "yes" | "no";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue