mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-06 22:07:58 +03:00
feat: enable batch manual errmsgs in client before uploading
this runs prudence schemas on the browser straightforward enough, required a little refactoring, but we're good.
This commit is contained in:
@@ -60,6 +60,7 @@
|
||||
"postcss-safe-parser": "4.0.1",
|
||||
"pretty-bytes": "5.6.0",
|
||||
"prop-types": "15.6.0",
|
||||
"prudence": "0.9.8",
|
||||
"react": "17.0.2",
|
||||
"react-async": "10.0.1",
|
||||
"react-bootstrap": "1.6.1",
|
||||
|
||||
@@ -2,8 +2,15 @@ import ImportFileInfo from "components/imports/ImportFileInfo";
|
||||
import useSetSubheader from "components/layout/header/useSetSubheader";
|
||||
import { TachiConfig } from "lib/config";
|
||||
import React, { useState } from "react";
|
||||
import { BatchManual, FormatGame, GetGameConfig, GetGamePTConfig } from "tachi-common";
|
||||
import { ImportStates, NotStartedState } from "types/import";
|
||||
import {
|
||||
BatchManual,
|
||||
FormatGame,
|
||||
FormatPrError,
|
||||
GetGameConfig,
|
||||
GetGamePTConfig,
|
||||
} from "tachi-common";
|
||||
import p from "prudence";
|
||||
import { PR_BATCH_MANUAL } from "tachi-common/lib/schemas";
|
||||
|
||||
export default function BatchManualPage() {
|
||||
useSetSubheader(["Dashboard", "Import Scores", "Batch Manual"]);
|
||||
@@ -32,6 +39,12 @@ export default function BatchManualPage() {
|
||||
);
|
||||
}
|
||||
|
||||
const err = p(data, PR_BATCH_MANUAL(data.meta.game, data.meta.playtype));
|
||||
|
||||
if (err) {
|
||||
throw new Error(FormatPrError(err, "Invalid BATCH-MANUAL: "));
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
info: {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"dependencies": {
|
||||
"@types/express-serve-static-core": "4.17.29",
|
||||
"@types/mongodb": "3.6.18",
|
||||
"deepmerge": "^4.2.2",
|
||||
"prudence": "0.9.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
+228
-1
@@ -6,9 +6,10 @@ import { allIDStrings, allImportTypes, allSupportedGames } from "../config/stati
|
||||
import { ALL_PERMISSIONS } from "../constants/permissions";
|
||||
import { GetGameConfig, GetGamePTConfig } from "../index";
|
||||
import { UserAuthLevels } from "../types";
|
||||
import deepmerge from "deepmerge";
|
||||
import p from "prudence";
|
||||
import type { GamePTConfig } from "../index";
|
||||
import type { Game, IDStrings, NotificationBody, Playtypes } from "../types";
|
||||
import type { Game, IDStrings, NotificationBody, Playtypes, Playtype } from "../types";
|
||||
import type {
|
||||
PrudenceSchema,
|
||||
ValidationFunctionParentOptionsKeychain,
|
||||
@@ -1186,3 +1187,229 @@ const PRE_SCHEMAS = {
|
||||
} as const;
|
||||
|
||||
export const SCHEMAS: Record<keyof typeof PRE_SCHEMAS, SchemaValidatorFunction> = PRE_SCHEMAS;
|
||||
|
||||
const BaseValidHitMeta = {
|
||||
fast: optNull(p.isPositiveInteger),
|
||||
slow: optNull(p.isPositiveInteger),
|
||||
maxCombo: optNull(p.isPositiveInteger),
|
||||
};
|
||||
|
||||
const PR_DP_RANDOM = (self: unknown) => {
|
||||
if (!Array.isArray(self) || self.length !== 2) {
|
||||
return "Expected an array with length 2";
|
||||
}
|
||||
|
||||
for (const a of self) {
|
||||
if (p.isIn("NONRAN", "RANDOM", "R-RANDOM", "S-RANDOM", "MIRROR")(a) !== true) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const PR_SCORE_META = (game: Game, playtype: Playtype): PrudenceSchema => {
|
||||
if (game === "iidx") {
|
||||
const random =
|
||||
playtype === "SP"
|
||||
? optNull(p.isIn("NONRAN", "RANDOM", "R-RANDOM", "S-RANDOM", "MIRROR"))
|
||||
: PR_DP_RANDOM;
|
||||
|
||||
return {
|
||||
random,
|
||||
assist: optNull(p.isIn("NO ASSIST", "AUTO SCRATCH", "LEGACY NOTE", "FULL ASSIST")),
|
||||
range: optNull(p.isIn("NONE", "SUDDEN+", "HIDDEN+", "SUD+ HID+", "LIFT", "LIFT SUD+")),
|
||||
gauge: optNull(p.isIn("ASSISTED EASY", "EASY", "NORMAL", "HARD", "EX HARD")),
|
||||
};
|
||||
} else if (game === "bms") {
|
||||
const random =
|
||||
playtype === "7K"
|
||||
? optNull(p.isIn("NONRAN", "RANDOM", "R-RANDOM", "S-RANDOM", "MIRROR"))
|
||||
: optNull(PR_DP_RANDOM);
|
||||
|
||||
return {
|
||||
random,
|
||||
inputDevice: optNull(p.isIn("KEYBOARD", "BM_CONTROLLER")),
|
||||
client: optNull(p.isIn("LR2", "lr2oraja")),
|
||||
};
|
||||
} else if (game === "usc") {
|
||||
return {
|
||||
noteMod: optNull(p.isIn("NORMAL", "MIRROR", "RANDOM", "MIR-RAN")),
|
||||
gaugeMod: optNull(p.isIn("NORMAL", "HARD", "PERMISSIVE")),
|
||||
};
|
||||
} else if (game === "sdvx") {
|
||||
return {
|
||||
inSkillAnalyser: "*?boolean",
|
||||
};
|
||||
} else if (game === "wacca") {
|
||||
return { mirror: "*?boolean" };
|
||||
} else if (game === "popn") {
|
||||
return {
|
||||
hiSpeed: optNull(p.isPositive),
|
||||
hidden: optNull(p.isInteger),
|
||||
sudden: optNull(p.isInteger),
|
||||
random: optNull(p.isIn("NONRAN", "MIRROR", "RANDOM", "S-RANDOM")),
|
||||
gauge: optNull(p.isIn("NORMAL", "EASY", "HARD", "DANGER")),
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
const PR_HIT_META = (game: Game): PrudenceSchema => {
|
||||
if (game === "iidx") {
|
||||
return {
|
||||
bp: optNull(p.isPositiveInteger),
|
||||
gauge: optNull(p.isBetween(0, 100)),
|
||||
gaugeHistory: optNull([p.isBetween(0, 100)]),
|
||||
scoreHistory: optNull([p.isPositiveInteger]),
|
||||
comboBreak: optNull(p.isPositiveInteger),
|
||||
gsm: optNull({
|
||||
EASY: [p.nullable(p.isBetween(0, 100))],
|
||||
NORMAL: [p.nullable(p.isBetween(0, 100))],
|
||||
HARD: [p.nullable(p.isBetween(0, 100))],
|
||||
EX_HARD: [p.nullable(p.isBetween(0, 100))],
|
||||
}),
|
||||
};
|
||||
} else if (game === "sdvx") {
|
||||
return {
|
||||
gauge: optNull(p.isBetween(0, 100)),
|
||||
};
|
||||
} else if (game === "usc") {
|
||||
return { gauge: optNull(p.isBetween(0, 1)) };
|
||||
} else if (game === "bms") {
|
||||
return {
|
||||
bp: optNull(p.isPositiveInteger),
|
||||
gauge: optNull(p.isBetween(0, 100)),
|
||||
lbd: optNull(p.isPositiveInteger),
|
||||
ebd: optNull(p.isPositiveInteger),
|
||||
lpr: optNull(p.isPositiveInteger),
|
||||
epr: optNull(p.isPositiveInteger),
|
||||
lgd: optNull(p.isPositiveInteger),
|
||||
egd: optNull(p.isPositiveInteger),
|
||||
lgr: optNull(p.isPositiveInteger),
|
||||
egr: optNull(p.isPositiveInteger),
|
||||
lpg: optNull(p.isPositiveInteger),
|
||||
epg: optNull(p.isPositiveInteger),
|
||||
};
|
||||
} else if (game === "popn") {
|
||||
return {
|
||||
gauge: optNull(p.isBetween(0, 100)),
|
||||
specificClearType: optNull(
|
||||
p.isIn(
|
||||
"failedUnknown",
|
||||
"failedCircle",
|
||||
"failedDiamond",
|
||||
"failedStar",
|
||||
"easyClear",
|
||||
"clearCircle",
|
||||
"clearDiamond",
|
||||
"clearStar",
|
||||
"fullComboCircle",
|
||||
"fullComboDiamond",
|
||||
"fullComboStar",
|
||||
"perfect"
|
||||
)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
const PR_BATCH_MANUAL_SCORE = (game: Game, playtype: Playtype): PrudenceSchema => {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const scoreChecker =
|
||||
game === "itg" || game === "gitadora" ? p.isBetween(0, 100) : p.isPositiveInteger;
|
||||
|
||||
return {
|
||||
score: scoreChecker,
|
||||
lamp: p.isIn(gptConfig.lamps),
|
||||
matchType: p.isIn(
|
||||
"songTitle",
|
||||
"ddrSongHash",
|
||||
"tachiSongID",
|
||||
"bmsChartHash",
|
||||
"itgChartHash",
|
||||
"inGameID",
|
||||
"uscChartHash",
|
||||
"popnChartHash"
|
||||
),
|
||||
identifier: "string",
|
||||
percent: game === "jubeat" ? p.isBetween(0, 120) : p.is(undefined),
|
||||
comment: optNull(p.isBoundedString(3, 240)),
|
||||
difficulty: "*?string",
|
||||
|
||||
// this is checked in converting instead
|
||||
// the lowest acceptable time is september 9th 2001 - this check saves people who dont
|
||||
// read any documentation and would otherwise submit garbage.
|
||||
timeAchieved: optNull(
|
||||
(self) =>
|
||||
(typeof self === "number" && self > 1_000_000_000_000) ||
|
||||
self === 0 ||
|
||||
"Expected a number greater than 1 Trillion - did you pass unix seconds instead of milliseconds?"
|
||||
),
|
||||
judgements: optNull((self) => {
|
||||
if (typeof self !== "object" || self === null) {
|
||||
return "Not a valid object.";
|
||||
}
|
||||
|
||||
for (const [key, v] of Object.entries(self)) {
|
||||
// @ts-expect-error Frustrating check as it's a string but not a judgement so it's not really
|
||||
// happy with it.
|
||||
if (!gptConfig.judgements.includes(key)) {
|
||||
return `Invalid Key ${key}. Expected any of ${gptConfig.judgements.join(", ")}`;
|
||||
}
|
||||
|
||||
if ((!Number.isSafeInteger(v) || v < 0) && v !== null) {
|
||||
return `Key ${key} had an invalid value of ${v} [type: ${typeof v}]`;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
hitMeta: optNull(
|
||||
deepmerge(BaseValidHitMeta, PR_HIT_META(game)) as unknown as ValidSchemaValue
|
||||
),
|
||||
scoreMeta: optNull(PR_SCORE_META(game, playtype)),
|
||||
};
|
||||
};
|
||||
|
||||
// both iidx sp and dp share dans.
|
||||
const IIDXStringDans = GetGamePTConfig("iidx", "SP").classHumanisedFormat.dan.map((e) => e.id);
|
||||
const SDVXStringDans = GetGamePTConfig("sdvx", "Single").classHumanisedFormat.dan.map((e) => e.id);
|
||||
const WaccaStringStageUps = GetGamePTConfig("wacca", "Single").classHumanisedFormat.stageUp.map(
|
||||
(e) => e.id
|
||||
);
|
||||
|
||||
const PR_BATCH_MANUAL_CLASSES = (game: Game): PrudenceSchema => {
|
||||
// This can be implemented for any non-static class (i.e. dans).
|
||||
switch (game) {
|
||||
case "iidx":
|
||||
return {
|
||||
dan: optNull(p.isIn(IIDXStringDans)),
|
||||
};
|
||||
case "sdvx":
|
||||
return {
|
||||
dan: optNull(p.isIn(SDVXStringDans)),
|
||||
};
|
||||
case "wacca":
|
||||
return {
|
||||
stageUp: optNull(p.isIn(WaccaStringStageUps)),
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export const PR_BATCH_MANUAL = (game: Game, playtype: Playtype): PrudenceSchema => ({
|
||||
meta: {
|
||||
service: p.isBoundedString(3, 60),
|
||||
game: p.isIn(allSupportedGames),
|
||||
playtype: p.is(playtype),
|
||||
version: "*?string",
|
||||
},
|
||||
scores: [PR_BATCH_MANUAL_SCORE(game, playtype)],
|
||||
classes: optNull(PR_BATCH_MANUAL_CLASSES(game)),
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { GetGameConfig, GetGamePTConfig } from "../config/config";
|
||||
import type { ChartDocument, Game, IDStrings, Playtypes, SongDocument } from "..";
|
||||
import type { Grades, integer } from "../types";
|
||||
import type { PrudenceError } from "prudence";
|
||||
|
||||
export function FormatInt(v: number): string {
|
||||
return v.toString();
|
||||
@@ -263,3 +264,17 @@ export function CreateSongMap<G extends Game = Game>(songs: Array<SongDocument<G
|
||||
|
||||
return songMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a PrudenceError into something a little more readable.
|
||||
* @param err - The prudence error to format.
|
||||
* @param foreword - A description of what kind of error this was. Defaults to "Error".
|
||||
*/
|
||||
export function FormatPrError(err: PrudenceError, foreword = "Error"): string {
|
||||
const receivedText =
|
||||
typeof err.userVal === "object" && err.userVal !== null
|
||||
? ""
|
||||
: ` | Received ${err.userVal} [${err.userVal === null ? "null" : typeof err.userVal}]`;
|
||||
|
||||
return `${foreword}: ${err.keychain} | ${err.message}${receivedText}.`;
|
||||
}
|
||||
|
||||
Generated
+4
@@ -132,6 +132,7 @@ importers:
|
||||
postcss-safe-parser: 4.0.1
|
||||
pretty-bytes: 5.6.0
|
||||
prop-types: 15.6.0
|
||||
prudence: 0.9.8
|
||||
react: 17.0.2
|
||||
react-async: 10.0.1
|
||||
react-bootstrap: 1.6.1
|
||||
@@ -192,6 +193,7 @@ importers:
|
||||
postcss-safe-parser: 4.0.1
|
||||
pretty-bytes: 5.6.0
|
||||
prop-types: 15.6.0
|
||||
prudence: 0.9.8
|
||||
react: 17.0.2
|
||||
react-async: 10.0.1_react@17.0.2
|
||||
react-bootstrap: 1.6.1_alyzgwsqb3v5agqihhenzpdwfm
|
||||
@@ -233,11 +235,13 @@ importers:
|
||||
'@types/express-serve-static-core': 4.17.29
|
||||
'@types/mongodb': 3.6.18
|
||||
'@types/tap': ^15.0.7
|
||||
deepmerge: ^4.2.2
|
||||
prudence: 0.9.8
|
||||
tap: 15.1.6
|
||||
dependencies:
|
||||
'@types/express-serve-static-core': 4.17.29
|
||||
'@types/mongodb': 3.6.18
|
||||
deepmerge: 4.2.2
|
||||
prudence: 0.9.8
|
||||
devDependencies:
|
||||
'@types/tap': 15.0.7
|
||||
|
||||
@@ -1,245 +1,16 @@
|
||||
import { CreateBatchManualClassHandler } from "./class-handler";
|
||||
import ScoreImportFatalError from "../../../framework/score-importing/score-import-error";
|
||||
import deepmerge from "deepmerge";
|
||||
import { TachiConfig } from "lib/setup/config";
|
||||
import p from "prudence";
|
||||
import { GetGameConfig, GetGamePTConfig } from "tachi-common";
|
||||
import { GetGameConfig } from "tachi-common";
|
||||
import { PR_BATCH_MANUAL } from "tachi-common/lib/schemas";
|
||||
import { IsRecord, IsValidGame, IsValidPlaytype } from "utils/misc";
|
||||
import { FormatPrError } from "utils/prudence";
|
||||
import type { ParserFunctionReturns } from "../types";
|
||||
import type { BatchManualContext } from "./types";
|
||||
import type { KtLogger } from "lib/logger/logger";
|
||||
import type { PrudenceSchema, ValidSchemaValue } from "prudence";
|
||||
import type { BatchManual, BatchManualScore, Game, ImportTypes, Playtype } from "tachi-common";
|
||||
|
||||
const optNull = (v: ValidSchemaValue) => p.optional(p.nullable(v));
|
||||
|
||||
const BaseValidHitMeta = {
|
||||
fast: optNull(p.isPositiveInteger),
|
||||
slow: optNull(p.isPositiveInteger),
|
||||
maxCombo: optNull(p.isPositiveInteger),
|
||||
};
|
||||
|
||||
const PR_DP_RANDOM = (self: unknown) => {
|
||||
if (!Array.isArray(self) || self.length !== 2) {
|
||||
return "Expected an array with length 2";
|
||||
}
|
||||
|
||||
for (const a of self) {
|
||||
if (p.isIn("NONRAN", "RANDOM", "R-RANDOM", "S-RANDOM", "MIRROR")(a) !== true) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const PR_SCORE_META = (game: Game, playtype: Playtype): PrudenceSchema => {
|
||||
if (game === "iidx") {
|
||||
const random =
|
||||
playtype === "SP"
|
||||
? optNull(p.isIn("NONRAN", "RANDOM", "R-RANDOM", "S-RANDOM", "MIRROR"))
|
||||
: PR_DP_RANDOM;
|
||||
|
||||
return {
|
||||
random,
|
||||
assist: optNull(p.isIn("NO ASSIST", "AUTO SCRATCH", "LEGACY NOTE", "FULL ASSIST")),
|
||||
range: optNull(p.isIn("NONE", "SUDDEN+", "HIDDEN+", "SUD+ HID+", "LIFT", "LIFT SUD+")),
|
||||
gauge: optNull(p.isIn("ASSISTED EASY", "EASY", "NORMAL", "HARD", "EX HARD")),
|
||||
};
|
||||
} else if (game === "bms") {
|
||||
const random =
|
||||
playtype === "7K"
|
||||
? optNull(p.isIn("NONRAN", "RANDOM", "R-RANDOM", "S-RANDOM", "MIRROR"))
|
||||
: optNull(PR_DP_RANDOM);
|
||||
|
||||
return {
|
||||
random,
|
||||
inputDevice: optNull(p.isIn("KEYBOARD", "BM_CONTROLLER")),
|
||||
client: optNull(p.isIn("LR2", "lr2oraja")),
|
||||
};
|
||||
} else if (game === "usc") {
|
||||
return {
|
||||
noteMod: optNull(p.isIn("NORMAL", "MIRROR", "RANDOM", "MIR-RAN")),
|
||||
gaugeMod: optNull(p.isIn("NORMAL", "HARD", "PERMISSIVE")),
|
||||
};
|
||||
} else if (game === "sdvx") {
|
||||
return {
|
||||
inSkillAnalyser: "*?boolean",
|
||||
};
|
||||
} else if (game === "wacca") {
|
||||
return { mirror: "*?boolean" };
|
||||
} else if (game === "popn") {
|
||||
return {
|
||||
hiSpeed: optNull(p.isPositive),
|
||||
hidden: optNull(p.isInteger),
|
||||
sudden: optNull(p.isInteger),
|
||||
random: optNull(p.isIn("NONRAN", "MIRROR", "RANDOM", "S-RANDOM")),
|
||||
gauge: optNull(p.isIn("NORMAL", "EASY", "HARD", "DANGER")),
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
const PR_HIT_META = (game: Game): PrudenceSchema => {
|
||||
if (game === "iidx") {
|
||||
return {
|
||||
bp: optNull(p.isPositiveInteger),
|
||||
gauge: optNull(p.isBetween(0, 100)),
|
||||
gaugeHistory: optNull([p.isBetween(0, 100)]),
|
||||
scoreHistory: optNull([p.isPositiveInteger]),
|
||||
comboBreak: optNull(p.isPositiveInteger),
|
||||
gsm: optNull({
|
||||
EASY: [p.nullable(p.isBetween(0, 100))],
|
||||
NORMAL: [p.nullable(p.isBetween(0, 100))],
|
||||
HARD: [p.nullable(p.isBetween(0, 100))],
|
||||
EX_HARD: [p.nullable(p.isBetween(0, 100))],
|
||||
}),
|
||||
};
|
||||
} else if (game === "sdvx") {
|
||||
return {
|
||||
gauge: optNull(p.isBetween(0, 100)),
|
||||
};
|
||||
} else if (game === "usc") {
|
||||
return { gauge: optNull(p.isBetween(0, 1)) };
|
||||
} else if (game === "bms") {
|
||||
return {
|
||||
bp: optNull(p.isPositiveInteger),
|
||||
gauge: optNull(p.isBetween(0, 100)),
|
||||
lbd: optNull(p.isPositiveInteger),
|
||||
ebd: optNull(p.isPositiveInteger),
|
||||
lpr: optNull(p.isPositiveInteger),
|
||||
epr: optNull(p.isPositiveInteger),
|
||||
lgd: optNull(p.isPositiveInteger),
|
||||
egd: optNull(p.isPositiveInteger),
|
||||
lgr: optNull(p.isPositiveInteger),
|
||||
egr: optNull(p.isPositiveInteger),
|
||||
lpg: optNull(p.isPositiveInteger),
|
||||
epg: optNull(p.isPositiveInteger),
|
||||
};
|
||||
} else if (game === "popn") {
|
||||
return {
|
||||
gauge: optNull(p.isBetween(0, 100)),
|
||||
specificClearType: optNull(
|
||||
p.isIn(
|
||||
"failedUnknown",
|
||||
"failedCircle",
|
||||
"failedDiamond",
|
||||
"failedStar",
|
||||
"easyClear",
|
||||
"clearCircle",
|
||||
"clearDiamond",
|
||||
"clearStar",
|
||||
"fullComboCircle",
|
||||
"fullComboDiamond",
|
||||
"fullComboStar",
|
||||
"perfect"
|
||||
)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
const PR_BATCH_MANUAL_SCORE = (game: Game, playtype: Playtype): PrudenceSchema => {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const scoreChecker =
|
||||
game === "itg" || game === "gitadora" ? p.isBetween(0, 100) : p.isPositiveInteger;
|
||||
|
||||
return {
|
||||
score: scoreChecker,
|
||||
lamp: p.isIn(gptConfig.lamps),
|
||||
matchType: p.isIn(
|
||||
"songTitle",
|
||||
"ddrSongHash",
|
||||
"tachiSongID",
|
||||
"bmsChartHash",
|
||||
"itgChartHash",
|
||||
"inGameID",
|
||||
"uscChartHash",
|
||||
"popnChartHash"
|
||||
),
|
||||
identifier: "string",
|
||||
percent: game === "jubeat" ? p.isBetween(0, 120) : p.is(undefined),
|
||||
comment: optNull(p.isBoundedString(3, 240)),
|
||||
difficulty: "*?string",
|
||||
|
||||
// this is checked in converting instead
|
||||
// the lowest acceptable time is september 9th 2001 - this check saves people who dont
|
||||
// read any documentation and would otherwise submit garbage.
|
||||
timeAchieved: optNull(
|
||||
(self) =>
|
||||
(typeof self === "number" && self > 1_000_000_000_000) ||
|
||||
self === 0 ||
|
||||
"Expected a number greater than 1 Trillion - did you pass unix seconds instead of milliseconds?"
|
||||
),
|
||||
judgements: optNull((self) => {
|
||||
if (typeof self !== "object" || self === null) {
|
||||
return "Not a valid object.";
|
||||
}
|
||||
|
||||
for (const [key, v] of Object.entries(self)) {
|
||||
// @ts-expect-error Frustrating check as it's a string but not a judgement so it's not really
|
||||
// happy with it.
|
||||
if (!gptConfig.judgements.includes(key)) {
|
||||
return `Invalid Key ${key}. Expected any of ${gptConfig.judgements.join(", ")}`;
|
||||
}
|
||||
|
||||
if ((!Number.isSafeInteger(v) || v < 0) && v !== null) {
|
||||
return `Key ${key} had an invalid value of ${v} [type: ${typeof v}]`;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
hitMeta: optNull(
|
||||
deepmerge(BaseValidHitMeta, PR_HIT_META(game)) as unknown as ValidSchemaValue
|
||||
),
|
||||
scoreMeta: optNull(PR_SCORE_META(game, playtype)),
|
||||
};
|
||||
};
|
||||
|
||||
// both iidx sp and dp share dans.
|
||||
const IIDXStringDans = GetGamePTConfig("iidx", "SP").classHumanisedFormat.dan.map((e) => e.id);
|
||||
const SDVXStringDans = GetGamePTConfig("sdvx", "Single").classHumanisedFormat.dan.map((e) => e.id);
|
||||
const WaccaStringStageUps = GetGamePTConfig("wacca", "Single").classHumanisedFormat.stageUp.map(
|
||||
(e) => e.id
|
||||
);
|
||||
|
||||
const PR_BATCH_MANUAL_CLASSES = (game: Game): PrudenceSchema => {
|
||||
// This can be implemented for any non-static class (i.e. dans).
|
||||
switch (game) {
|
||||
case "iidx":
|
||||
return {
|
||||
dan: optNull(p.isIn(IIDXStringDans)),
|
||||
};
|
||||
case "sdvx":
|
||||
return {
|
||||
dan: optNull(p.isIn(SDVXStringDans)),
|
||||
};
|
||||
case "wacca":
|
||||
return {
|
||||
stageUp: optNull(p.isIn(WaccaStringStageUps)),
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const PR_BATCH_MANUAL = (game: Game, playtype: Playtype): PrudenceSchema => ({
|
||||
meta: {
|
||||
service: p.isBoundedString(3, 60),
|
||||
game: p.isIn(TachiConfig.GAMES),
|
||||
playtype: p.is(playtype),
|
||||
version: "*?string",
|
||||
},
|
||||
scores: [PR_BATCH_MANUAL_SCORE(game, playtype)],
|
||||
classes: optNull(PR_BATCH_MANUAL_CLASSES(game)),
|
||||
});
|
||||
|
||||
/**
|
||||
* Parses an object of BATCH-MANUAL data.
|
||||
* @param object - The object to parse.
|
||||
|
||||
Reference in New Issue
Block a user