s3si.ts/src/state.ts

122 lines
2.7 KiB
TypeScript
Raw Normal View History

import { DeepReadonly } from "../deps.ts";
import { DEFAULT_ENV, Env } from "./env.ts";
2022-10-18 08:08:26 -04:00
export type LoginState = {
2022-10-18 20:36:58 -04:00
sessionToken?: string;
gToken?: string;
bulletToken?: string;
2022-10-18 08:08:26 -04:00
};
2022-10-28 05:07:18 -04:00
export type RankState = {
// generated by gameId(battle.id)
2022-10-28 08:45:56 -04:00
// TODO: If the gameId does not exist, the tracker will assume that the user has
2022-10-28 05:07:18 -04:00
// not played a bankara match. And it will start tracking from the first match
2022-10-28 08:45:56 -04:00
gameId: string;
2022-10-28 05:07:18 -04:00
// C-, B, A+, S, S+0, S+12
rank: string;
rankPoint: number;
};
2022-10-18 09:16:51 -04:00
export type State = {
loginState?: LoginState;
2022-10-18 20:36:58 -04:00
fGen: string;
appUserAgent?: string;
2022-10-19 04:56:18 -04:00
userLang?: string;
userCountry?: string;
2022-10-20 09:45:59 -04:00
2022-10-28 05:07:18 -04:00
rankState?: RankState;
2022-10-20 09:45:59 -04:00
cacheDir: string;
// Exporter config
statInkApiKey?: string;
fileExportPath: string;
2022-10-22 18:52:08 -04:00
monitorInterval: number;
2022-10-18 09:16:51 -04:00
};
2022-10-18 20:36:58 -04:00
export const DEFAULT_STATE: State = {
2022-10-20 09:45:59 -04:00
cacheDir: "./cache",
2022-10-18 20:36:58 -04:00
fGen: "https://api.imink.app/f",
2022-10-20 09:45:59 -04:00
fileExportPath: "./export",
2022-10-22 18:52:08 -04:00
monitorInterval: 500,
2022-10-18 20:36:58 -04:00
};
2022-10-23 04:25:10 -04:00
export type StateBackend = {
read: () => Promise<State>;
write: (newState: State) => Promise<void>;
};
export class InMemoryStateBackend implements StateBackend {
state: State;
constructor(state?: State) {
this.state = state ?? DEFAULT_STATE;
}
read() {
return Promise.resolve(this.state);
}
write(newState: State) {
this.state = newState;
return Promise.resolve();
}
}
2022-10-23 04:25:10 -04:00
export class FileStateBackend implements StateBackend {
constructor(private path: string) {}
async read(): Promise<DeepReadonly<State>> {
2022-10-24 08:47:25 -04:00
const data = await Deno.readTextFile(this.path);
const json = JSON.parse(data);
2022-10-23 04:25:10 -04:00
return json;
}
async write(newState: State): Promise<void> {
2022-10-24 08:47:25 -04:00
const data = JSON.stringify(newState, undefined, 2);
2022-10-23 04:25:10 -04:00
const swapPath = `${this.path}.swap`;
2022-10-24 08:47:25 -04:00
await Deno.writeTextFile(swapPath, data);
2022-10-23 04:25:10 -04:00
await Deno.rename(swapPath, this.path);
}
}
export class Profile {
protected _state?: State;
protected stateBackend: StateBackend;
protected env: Env;
constructor(
{ stateBackend, env = DEFAULT_ENV }: {
stateBackend: StateBackend;
env?: Env;
},
) {
this.stateBackend = stateBackend;
this.env = env;
}
get state(): DeepReadonly<State> {
if (!this._state) {
throw new Error("state is not initialized");
}
return this._state;
}
async writeState(newState: State) {
this._state = newState;
await this.stateBackend.write(newState);
}
async readState() {
try {
const json = await this.stateBackend.read();
this._state = {
...DEFAULT_STATE,
...json,
};
} catch (e) {
this.env.logger.warn(
`Failed to read config file, create new config file. (${e})`,
);
await this.writeState(DEFAULT_STATE);
}
}
}