mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-05 20:56:13 -05:00
137 lines
3.5 KiB
TypeScript
137 lines
3.5 KiB
TypeScript
import { z } from "zod";
|
|
import gameMisc from "~/../locales/en/game-misc.json";
|
|
import type { TablesInsertable } from "~/db/tables";
|
|
import { stageIds } from "~/modules/in-game-lists/stage-ids";
|
|
import type { RankedModeShort } from "~/modules/in-game-lists/types";
|
|
import { SPLATOON3_INK_SCHEDULES_URL } from "~/utils/urls";
|
|
|
|
const STAGE_NAME_TO_ID = Object.fromEntries(
|
|
stageIds.map((id) => [gameMisc[`STAGE_${id}` as keyof typeof gameMisc], id]),
|
|
) as Record<string, number>;
|
|
|
|
const RULE_TO_MODE: Record<string, RankedModeShort> = {
|
|
AREA: "SZ",
|
|
LOFT: "TC",
|
|
GOAL: "RM",
|
|
CLAM: "CB",
|
|
};
|
|
|
|
const vsStageSchema = z.object({
|
|
name: z.string(),
|
|
image: z.object({ url: z.string() }),
|
|
});
|
|
|
|
const vsRuleSchema = z.object({
|
|
name: z.string(),
|
|
rule: z.string(),
|
|
});
|
|
|
|
const bankaraMatchSettingSchema = z.object({
|
|
vsStages: z.array(vsStageSchema),
|
|
vsRule: vsRuleSchema,
|
|
bankaraMode: z.enum(["CHALLENGE", "OPEN"]),
|
|
});
|
|
|
|
const bankaraNodeSchema = z.object({
|
|
startTime: z.string(),
|
|
endTime: z.string(),
|
|
bankaraMatchSettings: z.array(bankaraMatchSettingSchema).nullable(),
|
|
});
|
|
|
|
const xMatchSettingSchema = z
|
|
.object({
|
|
vsStages: z.array(vsStageSchema),
|
|
vsRule: vsRuleSchema,
|
|
})
|
|
.nullable();
|
|
|
|
const xNodeSchema = z.object({
|
|
startTime: z.string(),
|
|
endTime: z.string(),
|
|
xMatchSetting: xMatchSettingSchema,
|
|
});
|
|
|
|
const schedulesSchema = z.object({
|
|
data: z.object({
|
|
bankaraSchedules: z.object({
|
|
nodes: z.array(bankaraNodeSchema),
|
|
}),
|
|
xSchedules: z.object({
|
|
nodes: z.array(xNodeSchema),
|
|
}),
|
|
}),
|
|
});
|
|
|
|
function resolveStageId(stageName: string): number | null {
|
|
return STAGE_NAME_TO_ID[stageName] ?? null;
|
|
}
|
|
|
|
function resolveMode(rule: string): RankedModeShort | null {
|
|
return RULE_TO_MODE[rule] ?? null;
|
|
}
|
|
|
|
export async function fetchRotations(): Promise<
|
|
Omit<TablesInsertable["SplatoonRotation"], "id">[]
|
|
> {
|
|
const response = await fetch(SPLATOON3_INK_SCHEDULES_URL, {
|
|
headers: { "User-Agent": "sendou.ink" },
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
`Failed to fetch schedules: ${response.status} ${response.statusText}`,
|
|
);
|
|
}
|
|
|
|
const json = await response.json();
|
|
const parsed = schedulesSchema.parse(json);
|
|
|
|
const rotations: Omit<TablesInsertable["SplatoonRotation"], "id">[] = [];
|
|
|
|
for (const node of parsed.data.bankaraSchedules.nodes) {
|
|
if (!node.bankaraMatchSettings) continue;
|
|
|
|
for (const setting of node.bankaraMatchSettings) {
|
|
const mode = resolveMode(setting.vsRule.rule);
|
|
if (!mode) continue;
|
|
|
|
const stageId1 = resolveStageId(setting.vsStages[0]?.name ?? "");
|
|
const stageId2 = resolveStageId(setting.vsStages[1]?.name ?? "");
|
|
if (stageId1 === null || stageId2 === null) continue;
|
|
|
|
const type = setting.bankaraMode === "CHALLENGE" ? "SERIES" : "OPEN";
|
|
|
|
rotations.push({
|
|
type,
|
|
mode,
|
|
stageId1,
|
|
stageId2,
|
|
startTime: Math.floor(new Date(node.startTime).getTime() / 1000),
|
|
endTime: Math.floor(new Date(node.endTime).getTime() / 1000),
|
|
});
|
|
}
|
|
}
|
|
|
|
for (const node of parsed.data.xSchedules.nodes) {
|
|
if (!node.xMatchSetting) continue;
|
|
|
|
const mode = resolveMode(node.xMatchSetting.vsRule.rule);
|
|
if (!mode) continue;
|
|
|
|
const stageId1 = resolveStageId(node.xMatchSetting.vsStages[0]?.name ?? "");
|
|
const stageId2 = resolveStageId(node.xMatchSetting.vsStages[1]?.name ?? "");
|
|
if (stageId1 === null || stageId2 === null) continue;
|
|
|
|
rotations.push({
|
|
type: "X",
|
|
mode,
|
|
stageId1,
|
|
stageId2,
|
|
startTime: Math.floor(new Date(node.startTime).getTime() / 1000),
|
|
endTime: Math.floor(new Date(node.endTime).getTime() / 1000),
|
|
});
|
|
}
|
|
|
|
return rotations;
|
|
}
|