Merge remote-tracking branch 'upstream/main' into dumb-splashcat-thing

dumb-splashcat-thing
Rosalina 2023-06-14 22:20:48 -04:00
commit edda191f56
No known key found for this signature in database
12 changed files with 209 additions and 42 deletions

View File

@ -1,3 +1,16 @@
## 0.4.4
feat: send Anarchy (Open) Power
## 0.4.3
feat: add `list-method` option
([#73](https://github.com/spacemeowx2/s3si.ts/issues/73))
## 0.4.2
fix: `coral_user_id` is string
## 0.4.1
feat: add support for Challenges

View File

@ -20,12 +20,16 @@ Options:
--exporter <exporter>, -e Exporter list to use (default: stat.ink)
Multiple exporters can be separated by commas
(e.g. "stat.ink,file")
--list-method When set to "latest", the latest 50 matches will be obtained.
When set to "all", matches of all modes will be obtained with a maximum of 250 matches (5 modes x 50 matches).
When set to "auto", the latest 50 matches will be obtained. If 50 matches have not been uploaded yet, matches will be obtained from the list of all modes.
"auto" is the default setting.
--no-progress, -n Disable progress bar
--monitor, -m Monitor mode
--skip-mode <mode>, -s Skip mode (default: null)
("vs", "coop")
--with-summary Include summary in the output
--help Show this help message and exit`,
--help Show this help message and exit
```
3. If it's your first time running this, follow the instructions to login to

View File

@ -8,7 +8,7 @@
},
"package": {
"productName": "s3si-ts",
"version": "0.4.1"
"version": "0.4.4"
},
"tauri": {
"allowlist": {

View File

@ -4,7 +4,7 @@ import { flags } from "./deps.ts";
const parseArgs = (args: string[]) => {
const parsed = flags.parse(args, {
string: ["profilePath", "exporter", "skipMode"],
string: ["profilePath", "exporter", "skipMode", "listMethod"],
boolean: ["help", "noProgress", "monitor", "withSummary"],
alias: {
"help": "h",
@ -15,6 +15,7 @@ const parseArgs = (args: string[]) => {
"skipMode": ["s", "skip-mode"],
"withSummary": "with-summary",
"withStages": "with-stages",
"listMethod": "list-method",
},
});
return parsed;
@ -30,6 +31,10 @@ Options:
--exporter <exporter>, -e Exporter list to use (default: stat.ink)
Multiple exporters can be separated by commas
(e.g. "stat.ink,file,mongodb")
--list-method When set to "latest", the latest 50 matches will be obtained.
When set to "all", matches of all modes will be obtained with a maximum of 250 matches (5 modes x 50 matches).
When set to "auto", the latest 50 matches will be obtained. If 50 matches have not been uploaded yet, matches will be obtained from the list of all modes.
"auto" is the default setting.
--no-progress, -n Disable progress bar
--monitor, -m Monitor mode
--skip-mode <mode>, -s Skip mode (default: null)

View File

@ -5,7 +5,7 @@ import {
HistoryGroups,
RankParam,
} from "./types.ts";
import { gameId, parseHistoryDetailId } from "./utils.ts";
import { battleTime, gameId } from "./utils.ts";
import { getSeason } from "./VersionData.ts";
const splusParams = () => {
@ -193,17 +193,6 @@ function addRank(
};
}
const battleTime = (id: string) => {
const { timestamp } = parseHistoryDetailId(id);
const dateStr = timestamp.replace(
/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})/,
"$1-$2-$3T$4:$5:$6Z",
);
return new Date(dateStr);
};
type FlattenItem = {
id: string;
gameId: string;

View File

@ -1,8 +1,8 @@
import { loginManually } from "./iksm.ts";
import { MultiProgressBar } from "../deps.ts";
import { MultiProgressBar, Mutex } from "../deps.ts";
import { FileStateBackend, Profile, StateBackend } from "./state.ts";
import { Splatnet3 } from "./splatnet3.ts";
import { BattleListType, Game, GameExporter } from "./types.ts";
import { BattleListType, Game, GameExporter, ListMethod } from "./types.ts";
import { Cache, FileCache } from "./cache.ts";
import { StatInkExporter } from "./exporters/stat.ink.ts";
import { FileExporter } from "./exporters/file.ts";
@ -20,6 +20,7 @@ export type Opts = {
withSummary: boolean;
withStages: boolean;
skipMode?: string;
listMethod?: string;
cache?: Cache;
stateBackend?: StateBackend;
env: Env;
@ -32,6 +33,7 @@ export const DEFAULT_OPTS: Opts = {
monitor: false,
withSummary: false,
withStages: true,
listMethod: "latest",
env: DEFAULT_ENV,
};
@ -56,6 +58,103 @@ class StepProgress {
}
}
interface GameListFetcher {
/**
* Return not exported game list.
* [0] is the latest game.
* @param exporter GameExporter
*/
fetch(exporter: GameExporter): Promise<string[]>;
}
class BattleListFetcher implements GameListFetcher {
protected listMethod: ListMethod;
protected allBattleList?: string[];
protected latestBattleList?: string[];
protected allLock = new Mutex();
protected latestLock = new Mutex();
constructor(
listMethod: string,
protected splatnet: Splatnet3,
) {
if (listMethod === "all") {
this.listMethod = "all";
} else if (listMethod === "latest") {
this.listMethod = "latest";
} else {
this.listMethod = "auto";
}
}
protected getAllBattleList() {
return this.allLock.use(async () => {
if (!this.allBattleList) {
this.allBattleList = await this.splatnet.getAllBattleList();
}
return this.allBattleList;
});
}
protected getLatestBattleList() {
return this.latestLock.use(async () => {
if (!this.latestBattleList) {
this.latestBattleList = await this.splatnet.getBattleList();
}
return this.latestBattleList;
});
}
private async innerFetch(exporter: GameExporter) {
if (this.listMethod === "latest") {
return await exporter.notExported({
type: "VsInfo",
list: await this.getLatestBattleList(),
});
}
if (this.listMethod === "all") {
return await exporter.notExported({
type: "VsInfo",
list: await this.getAllBattleList(),
});
}
if (this.listMethod === "auto") {
const latestList = await exporter.notExported({
type: "VsInfo",
list: await this.getLatestBattleList(),
});
if (latestList.length === 50) {
return await exporter.notExported({
type: "VsInfo",
list: await this.getAllBattleList(),
});
}
return latestList;
}
throw new TypeError(`Unknown listMethod: ${this.listMethod}`);
}
async fetch(exporter: GameExporter) {
return [...await this.innerFetch(exporter)].reverse();
}
}
class CoopListFetcher implements GameListFetcher {
constructor(
protected splatnet: Splatnet3,
) {}
async fetch(exporter: GameExporter) {
return [
...await exporter.notExported({
type: "CoopInfo",
list: await this.splatnet.getBattleList(BattleListType.Coop),
}),
].reverse();
}
}
function progress({ total, currentUrl, done }: StepProgress): Progress {
return {
total,
@ -76,6 +175,12 @@ export class App {
env: opts.env,
});
this.env = opts.env;
if (
opts.listMethod && !["all", "auto", "latest"].includes(opts.listMethod)
) {
throw new TypeError(`Unknown listMethod: ${opts.listMethod}`);
}
}
getSkipMode(): ("vs" | "coop")[] {
@ -193,8 +298,10 @@ export class App {
if (skipMode.includes("vs") || exporters.length === 0) {
this.env.logger.log("Skip exporting VS games.");
} else {
this.env.logger.log("Fetching battle list...");
const gameList = await splatnet.getBattleList();
const gameListFetcher = new BattleListFetcher(
this.opts.listMethod ?? "auto",
splatnet,
);
const { redraw, endBar } = this.exporterProgress("Export vs games");
const fetcher = new GameFetcher({
@ -213,7 +320,7 @@ export class App {
type: "VsInfo",
fetcher,
exporter: e,
gameList,
gameListFetcher,
stepProgress: stats[e.name],
onStep: () => {
redraw(e.name, progress(stats[e.name]));
@ -247,10 +354,7 @@ export class App {
if (skipMode.includes("coop") || exporters.length === 0) {
this.env.logger.log("Skip exporting coop games.");
} else {
this.env.logger.log("Fetching coop battle list...");
const coopBattleList = await splatnet.getBattleList(
BattleListType.Coop,
);
const gameListFetcher = new CoopListFetcher(splatnet);
const { redraw, endBar } = this.exporterProgress("Export coop games");
const fetcher = new GameFetcher({
@ -267,7 +371,7 @@ export class App {
type: "CoopInfo",
fetcher,
exporter: e,
gameList: coopBattleList,
gameListFetcher,
stepProgress: stats[e.name],
onStep: () => {
redraw(e.name, progress(stats[e.name]));
@ -401,30 +505,24 @@ export class App {
* @param gameList ID list of games, sorted by date, newest first
* @param onStep Callback function called when a game is exported
*/
async exportGameList({
private async exportGameList({
type,
fetcher,
exporter,
gameList,
gameListFetcher,
stepProgress,
onStep,
}: {
type: Game["type"];
exporter: GameExporter;
fetcher: GameFetcher;
gameList: string[];
gameListFetcher: GameListFetcher;
stepProgress: StepProgress;
onStep: () => void;
}): Promise<StepProgress> {
onStep?.();
const workQueue = [
...await exporter.notExported({
type,
list: gameList,
}),
]
.reverse();
const workQueue = await gameListFetcher.fetch(exporter);
const step = async (id: string) => {
const detail = await fetcher.fetch(type, id);

View File

@ -2,9 +2,9 @@ import type { StatInkPostBody, VsHistoryDetail } from "./types.ts";
export const AGENT_NAME = "splashcat / s3si.ts";
export const AGENT_VERSION = "1.1.1";
export const S3SI_VERSION = "0.4.1";
export const S3SI_VERSION = "0.4.4";
export const COMBINED_VERSION = `${AGENT_VERSION}/${S3SI_VERSION}`;
export const NSOAPP_VERSION = "2.5.1";
export const NSOAPP_VERSION = "2.5.2";
export const WEB_VIEW_VERSION = "4.0.0-d5178440";
export enum Queries {
HomeQuery = "7dcc64ea27a08e70919893a0d3f70871",
@ -12,6 +12,7 @@ export enum Queries {
RegularBattleHistoriesQuery = "3baef04b095ad8975ea679d722bc17de",
BankaraBattleHistoriesQuery = "0438ea6978ae8bd77c5d1250f4f84803",
XBattleHistoriesQuery = "6796e3cd5dc3ebd51864dc709d899fc5",
EventBattleHistoriesQuery = "9744fcf676441873c7c8a51285b6aa4d",
PrivateBattleHistoriesQuery = "8e5ae78b194264a6c230e262d069bd28",
VsHistoryDetailQuery = "9ee0099fbe3d8db2a838a75cf42856dd",
CoopHistoryQuery = "91b917becd2fa415890f5b47e15ffb15",

View File

@ -588,6 +588,8 @@ export class StatInkExporter implements GameExporter {
}
}
result.bankara_power_after = vsDetail.bankaraMatch?.bankaraPower?.power;
if (rankBeforeState && rankState) {
result.rank_before_exp = rankBeforeState.rankPoint;
result.rank_after_exp = rankState.rankPoint;

View File

@ -213,20 +213,20 @@ export async function getGToken(
const idToken2: string = respJson?.result?.webApiServerCredential
?.accessToken;
const coralUserId: number = respJson?.result?.user?.id;
const coralUserId: string = respJson?.result?.user?.id?.toString();
if (!idToken2 || !coralUserId) {
throw new APIError({
response: resp,
json: respJson,
message:
`No idToken2 or coralUserId found. Please try again later. ('${idToken2}', '${coralUserId}')`,
`No idToken2 or coralUserId found. Please try again later. (${idToken2.length}, ${coralUserId.length})`,
});
}
return [idToken2, coralUserId] as const;
};
const getGToken = async (idToken: string, coralUserId: number) => {
const getGToken = async (idToken: string, coralUserId: string) => {
const { f, request_id: requestId, timestamp } = await callImink({
step: 2,
idToken,
@ -414,7 +414,7 @@ async function callImink(
step: number;
idToken: string;
userId: string;
coralUserId?: number;
coralUserId?: string;
env: Env;
},
): Promise<IminkResponse> {

View File

@ -15,7 +15,7 @@ import {
} from "./types.ts";
import { DEFAULT_ENV, Env } from "./env.ts";
import { getBulletToken, getGToken } from "./iksm.ts";
import { parseHistoryDetailId } from "./utils.ts";
import { battleTime, parseHistoryDetailId } from "./utils.ts";
export class Splatnet3 {
protected profile: Profile;
@ -137,6 +137,12 @@ export class Splatnet3 {
[BattleListType.Bankara]: () =>
this.request(Queries.BankaraBattleHistoriesQuery)
.then((r) => getIdsFromGroups(r.bankaraBattleHistories)),
[BattleListType.XBattle]: () =>
this.request(Queries.XBattleHistoriesQuery)
.then((r) => getIdsFromGroups(r.xBattleHistories)),
[BattleListType.Event]: () =>
this.request(Queries.EventBattleHistoriesQuery)
.then((r) => getIdsFromGroups(r.eventBattleHistories)),
[BattleListType.Private]: () =>
this.request(Queries.PrivateBattleHistoriesQuery)
.then((r) => getIdsFromGroups(r.privateBattleHistories)),
@ -168,6 +174,29 @@ export class Splatnet3 {
return await this.BATTLE_LIST_TYPE_MAP[battleListType]();
}
// Get all id from all battle list, sort by time, [0] is the latest
async getAllBattleList() {
const ALL_TYPE: BattleListType[] = [
BattleListType.Regular,
BattleListType.Bankara,
BattleListType.XBattle,
BattleListType.Event,
BattleListType.Private,
];
const ids: string[] = [];
for (const type of ALL_TYPE) {
ids.push(...await this.getBattleList(type));
}
const timeMap = new Map<string, Date>(
ids.map((id) => [id, battleTime(id)] as const),
);
return ids.sort((a, b) =>
timeMap.get(b)!.getTime() - timeMap.get(a)!.getTime()
);
}
getBattleDetail(
id: string,
) {

View File

@ -9,6 +9,7 @@ export type VarsMap = {
[Queries.RegularBattleHistoriesQuery]: [];
[Queries.BankaraBattleHistoriesQuery]: [];
[Queries.XBattleHistoriesQuery]: [];
[Queries.EventBattleHistoriesQuery]: [];
[Queries.PrivateBattleHistoriesQuery]: [];
[Queries.VsHistoryDetailQuery]: [{
vsResultId: string;
@ -244,6 +245,9 @@ export type VsHistoryDetail = {
bankaraMatch: {
earnedUdemaePoint: null | number;
mode: "OPEN" | "CHALLENGE";
bankaraPower?: null | {
power?: null | number;
};
} | null;
festMatch: {
dragonMatchType: "NORMAL" | "DECUPLE" | "DRAGON" | "DOUBLE_DRAGON";
@ -427,6 +431,11 @@ export type RespMap = {
};
[Queries.BankaraBattleHistoriesQuery]: BankaraBattleHistories;
[Queries.XBattleHistoriesQuery]: XBattleHistories;
[Queries.EventBattleHistoriesQuery]: {
eventBattleHistories: {
historyGroups: HistoryGroups<BattleListNode>;
};
};
[Queries.PrivateBattleHistoriesQuery]: {
privateBattleHistories: {
historyGroups: HistoryGroups<BattleListNode>;
@ -614,10 +623,14 @@ export enum BattleListType {
Latest,
Regular,
Bankara,
Event,
XBattle,
Private,
Coop,
}
export type ListMethod = "latest" | "all" | "auto";
export type StatInkUuidList = {
status: number;
code: number;
@ -822,6 +835,8 @@ export type StatInkPostBody = {
challenge_lose?: number;
x_power_before?: number | null;
x_power_after?: number | null;
bankara_power_before?: number | null;
bankara_power_after?: number | null;
fest_power?: number; // Splatfest Power (Pro)
fest_dragon?:
| "10x"

View File

@ -188,3 +188,14 @@ export function urlSimplify(url: string): { pathname: string } | string {
return url;
}
}
export const battleTime = (id: string) => {
const { timestamp } = parseHistoryDetailId(id);
const dateStr = timestamp.replace(
/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})/,
"$1-$2-$3T$4:$5:$6Z",
);
return new Date(dateStr);
};