mirror of
https://github.com/zkldi/Tachi.git
synced 2026-09-29 02:18:09 +03:00
Merge pull request #819 from TNG-dev/zkldi/explicit-matchType
This commit is contained in:
@@ -30,6 +30,7 @@ import type {
|
||||
IDStrings,
|
||||
JudgementLookup,
|
||||
Lamps,
|
||||
MatchTypes,
|
||||
Playtype,
|
||||
Playtypes,
|
||||
ProfileRatingLookup,
|
||||
@@ -92,6 +93,13 @@ interface BaseGamePTConfig<I extends IDStrings> {
|
||||
|
||||
tierlists: Array<GPTTierlists[I]>;
|
||||
tierlistDescriptions: Record<GPTTierlists[I], string>;
|
||||
|
||||
/**
|
||||
* What "matchTypes" should this game support for batch-manual imports? This
|
||||
* allows us to disable things like "songTitle" resolutions for games like BMS,
|
||||
* where song titles are absolutely not guaranteed to be unique.
|
||||
*/
|
||||
supportedMatchTypes: Array<MatchTypes>;
|
||||
}
|
||||
|
||||
interface GamePTConfigWithESD<I extends IDStrings> extends BaseGamePTConfig<I> {
|
||||
@@ -411,6 +419,8 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
|
||||
"kt-EXHC":
|
||||
"The EX-HARD Clear tiers for Kamaitachi. These are adapted from multiple sources.",
|
||||
},
|
||||
|
||||
supportedMatchTypes: ["inGameID", "tachiSongID", "songTitle"],
|
||||
},
|
||||
"iidx:DP": {
|
||||
idString: "iidx:DP",
|
||||
@@ -606,6 +616,7 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
|
||||
tierlistDescriptions: {
|
||||
"dp-tier": "The unofficial DP tiers, taken from https://zasa.sakura.ne.jp/dp/run.php.",
|
||||
},
|
||||
supportedMatchTypes: ["inGameID", "tachiSongID", "songTitle"],
|
||||
},
|
||||
"chunithm:Single": {
|
||||
idString: "chunithm:Single",
|
||||
@@ -692,6 +703,8 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
|
||||
|
||||
tierlists: [],
|
||||
tierlistDescriptions: {},
|
||||
|
||||
supportedMatchTypes: ["inGameID", "songTitle", "tachiSongID"],
|
||||
},
|
||||
"sdvx:Single": {
|
||||
idString: "sdvx:Single",
|
||||
@@ -790,6 +803,8 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
|
||||
tierlistDescriptions: {
|
||||
clear: "Clearing values taken from the unofficial SDVX spreadsheet tierlists.",
|
||||
},
|
||||
|
||||
supportedMatchTypes: ["sdvxInGameID", "songTitle", "tachiSongID"],
|
||||
},
|
||||
"usc:Controller": {
|
||||
idString: "usc:Controller",
|
||||
@@ -875,6 +890,8 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
|
||||
|
||||
tierlists: [],
|
||||
tierlistDescriptions: {},
|
||||
|
||||
supportedMatchTypes: ["uscChartHash", "tachiSongID"],
|
||||
},
|
||||
"usc:Keyboard": {
|
||||
idString: "usc:Keyboard",
|
||||
@@ -960,6 +977,8 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
|
||||
|
||||
tierlists: [],
|
||||
tierlistDescriptions: {},
|
||||
|
||||
supportedMatchTypes: ["uscChartHash", "tachiSongID"],
|
||||
},
|
||||
"museca:Single": {
|
||||
idString: "museca:Single",
|
||||
@@ -1042,6 +1061,8 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
|
||||
|
||||
tierlists: [],
|
||||
tierlistDescriptions: {},
|
||||
|
||||
supportedMatchTypes: ["songTitle", "tachiSongID", "inGameID"],
|
||||
},
|
||||
"bms:7K": {
|
||||
idString: "bms:7K",
|
||||
@@ -1159,6 +1180,8 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
|
||||
"sgl-EC": "Sieglinde Easy Clear ratings.",
|
||||
"sgl-HC": "Sieglinde Hard Clear ratings.",
|
||||
},
|
||||
|
||||
supportedMatchTypes: ["bmsChartHash", "tachiSongID"],
|
||||
},
|
||||
"bms:14K": {
|
||||
idString: "bms:14K",
|
||||
@@ -1273,6 +1296,8 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
|
||||
"sgl-EC": "Sieglinde Easy Clear ratings.",
|
||||
"sgl-HC": "Sieglinde Hard Clear ratings.",
|
||||
},
|
||||
|
||||
supportedMatchTypes: ["bmsChartHash", "tachiSongID"],
|
||||
},
|
||||
"maimaidx:Single": {
|
||||
idString: "maimaidx:Single",
|
||||
@@ -1405,6 +1430,8 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
|
||||
|
||||
tierlists: [],
|
||||
tierlistDescriptions: {},
|
||||
|
||||
supportedMatchTypes: ["songTitle", "tachiSongID"],
|
||||
},
|
||||
"gitadora:Gita": {
|
||||
idString: "gitadora:Gita",
|
||||
@@ -1504,6 +1531,8 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
|
||||
|
||||
tierlists: [],
|
||||
tierlistDescriptions: {},
|
||||
|
||||
supportedMatchTypes: ["inGameID", "songTitle", "tachiSongID"],
|
||||
},
|
||||
"gitadora:Dora": {
|
||||
idString: "gitadora:Dora",
|
||||
@@ -1586,6 +1615,8 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
|
||||
|
||||
tierlists: [],
|
||||
tierlistDescriptions: {},
|
||||
|
||||
supportedMatchTypes: ["inGameID", "songTitle", "tachiSongID"],
|
||||
},
|
||||
"wacca:Single": {
|
||||
idString: "wacca:Single",
|
||||
@@ -1679,6 +1710,8 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
|
||||
|
||||
tierlists: [],
|
||||
tierlistDescriptions: {},
|
||||
|
||||
supportedMatchTypes: ["songTitle", "tachiSongID"],
|
||||
},
|
||||
"popn:9B": {
|
||||
idString: "popn:9B",
|
||||
@@ -1766,6 +1799,8 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
|
||||
|
||||
tierlists: [],
|
||||
tierlistDescriptions: {},
|
||||
|
||||
supportedMatchTypes: ["inGameID", "tachiSongID", "popnChartHash"],
|
||||
},
|
||||
"jubeat:Single": {
|
||||
idString: "jubeat:Single",
|
||||
@@ -1866,6 +1901,8 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
|
||||
|
||||
tierlists: [],
|
||||
tierlistDescriptions: {},
|
||||
|
||||
supportedMatchTypes: ["inGameID", "tachiSongID"],
|
||||
},
|
||||
"pms:Controller": {
|
||||
idString: "pms:Controller",
|
||||
@@ -1977,6 +2014,8 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
|
||||
"sgl-EC": "Sieglinde Easy Clear ratings.",
|
||||
"sgl-HC": "Sieglinde Hard Clear ratings.",
|
||||
},
|
||||
|
||||
supportedMatchTypes: ["bmsChartHash", "tachiSongID"],
|
||||
},
|
||||
"pms:Keyboard": {
|
||||
idString: "pms:Keyboard",
|
||||
@@ -2088,6 +2127,8 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
|
||||
"sgl-EC": "Sieglinde Easy Clear ratings.",
|
||||
"sgl-HC": "Sieglinde Hard Clear ratings.",
|
||||
},
|
||||
|
||||
supportedMatchTypes: ["bmsChartHash", "tachiSongID"],
|
||||
},
|
||||
"itg:Stamina": {
|
||||
idString: "itg:Stamina",
|
||||
@@ -2189,6 +2230,8 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
|
||||
|
||||
tierlists: [],
|
||||
tierlistDescriptions: {},
|
||||
|
||||
supportedMatchTypes: ["itgChartHash", "tachiSongID"],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
+16
-15
@@ -781,8 +781,8 @@ interface ChartDocumentData {
|
||||
"pms:Controller": CDDataPMS;
|
||||
"pms:Keyboard": CDDataPMS;
|
||||
"itg:Stamina": {
|
||||
chartHash: string;
|
||||
difficultyTag: "Beginner" | "Challenge" | "Easy" | "Hard" | "Medium";
|
||||
hashGSV3: string;
|
||||
difficultyTag: "Beginner" | "Challenge" | "Easy" | "Edit" | "Hard" | "Medium";
|
||||
breakdown: {
|
||||
detailed: string;
|
||||
partiallySimplified: string;
|
||||
@@ -791,13 +791,6 @@ interface ChartDocumentData {
|
||||
npsPerMeasure: Array<number>;
|
||||
notesPerMeasure: Array<number>;
|
||||
};
|
||||
tech: {
|
||||
crossovers: integer;
|
||||
jacks: integer;
|
||||
brackets: integer;
|
||||
footswitches: integer;
|
||||
sideswitches: integer;
|
||||
};
|
||||
length: number;
|
||||
charter: string;
|
||||
displayBPM: number;
|
||||
@@ -1246,9 +1239,9 @@ export type IRImportTypes =
|
||||
|
||||
export type ImportTypes = APIImportTypes | FileUploadImportTypes | IRImportTypes;
|
||||
|
||||
export interface ImportProcessInfoKTDataNotFound {
|
||||
export interface ImportProcessInfoSongOrChartNotFound {
|
||||
success: false;
|
||||
type: "KTDataNotFound";
|
||||
type: "SongOrChartNotFound";
|
||||
message: string;
|
||||
content: {
|
||||
data: unknown;
|
||||
@@ -1292,9 +1285,9 @@ export interface ImportProcessInfoInternalError {
|
||||
export type ImportProcessingInfo<I extends IDStrings = IDStrings> =
|
||||
| ImportProcessInfoInternalError
|
||||
| ImportProcessInfoInvalidDatapoint
|
||||
| ImportProcessInfoKTDataNotFound
|
||||
| ImportProcessInfoOrphanExists
|
||||
| ImportProcessInfoScoreImported<I>;
|
||||
| ImportProcessInfoScoreImported<I>
|
||||
| ImportProcessInfoSongOrChartNotFound;
|
||||
|
||||
export interface ImportStatistics {
|
||||
scoreCount: integer;
|
||||
@@ -1424,6 +1417,14 @@ export interface UserGameStatsSnapshot<I extends IDStrings = IDStrings>
|
||||
timestamp: integer;
|
||||
}
|
||||
|
||||
// These MatchTypes don't need `difficulty` set in the batch manual.
|
||||
type MatchTypesNoDifficulty = "bmsChartHash" | "itgChartHash" | "popnChartHash" | "uscChartHash";
|
||||
|
||||
// These MatchTypes need `difficulty` set in the batch manual.
|
||||
type MatchTypesWithDifficulty = "inGameID" | "sdvxInGameID" | "songTitle" | "tachiSongID";
|
||||
|
||||
export type MatchTypes = MatchTypesNoDifficulty | MatchTypesWithDifficulty;
|
||||
|
||||
export type BatchManualScore<I extends IDStrings = IDStrings> = {
|
||||
score: number;
|
||||
lamp: Lamps[I];
|
||||
@@ -1436,11 +1437,11 @@ export type BatchManualScore<I extends IDStrings = IDStrings> = {
|
||||
scoreMeta?: Partial<ScoreMetaLookup[I]>;
|
||||
} & (
|
||||
| {
|
||||
matchType: "bmsChartHash" | "itgChartHash" | "popnChartHash" | "uscChartHash";
|
||||
matchType: MatchTypesNoDifficulty;
|
||||
difficulty?: undefined; // hack to stop ts from screaming when this is accessed sometimes
|
||||
}
|
||||
| {
|
||||
matchType: "inGameID" | "sdvxInGameID" | "songTitle" | "tachiSongID";
|
||||
matchType: MatchTypesWithDifficulty;
|
||||
difficulty: Difficulties[I];
|
||||
}
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -15,11 +15,6 @@ type DuplicateKeyDecl = string | string[];
|
||||
const SongChartKeys: Record<`${"song" | "chart"}s-${Game}`, DuplicateKeyDecl[]> = {};
|
||||
|
||||
for (const game of allSupportedGames) {
|
||||
// temporary hack
|
||||
if (game === "gitadora") {
|
||||
continue;
|
||||
}
|
||||
|
||||
SongChartKeys[`songs-${game}`] = ["id"];
|
||||
SongChartKeys[`charts-${game}`] = [
|
||||
"chartID",
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import chalk from "chalk";
|
||||
import {
|
||||
ChartDocument,
|
||||
FormatGame,
|
||||
Game,
|
||||
GetGameConfig,
|
||||
GetGamePTConfig,
|
||||
MatchTypes,
|
||||
Playtype,
|
||||
SongDocument,
|
||||
} from "tachi-common";
|
||||
import { allSupportedGames } from "tachi-common/config/static-config";
|
||||
import { ReadCollection } from "../util";
|
||||
|
||||
// check that a given matchType works for a given game.
|
||||
const uniquenessChecks: Array<{ game: Game; playtype: Playtype; matchType: MatchTypes }> = [];
|
||||
|
||||
for (const game of allSupportedGames) {
|
||||
for (const playtype of GetGameConfig(game).validPlaytypes) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
for (const matchType of gptConfig.supportedMatchTypes) {
|
||||
uniquenessChecks.push({ game, playtype, matchType });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve a unique identifer (or array of identifiers that must be globally unique)
|
||||
// for this match type to work
|
||||
const MATCH_TYPE_CHECKS: Record<
|
||||
MatchTypes,
|
||||
| {
|
||||
type: "SONGS";
|
||||
fn: (s: SongDocument) => string | Array<string>;
|
||||
}
|
||||
| {
|
||||
type: "CHARTS";
|
||||
fn: (c: any) => string | Array<string>;
|
||||
}
|
||||
> = {
|
||||
tachiSongID: { type: "CHARTS", fn: (s) => `${s.songID}-${s.difficulty}` },
|
||||
songTitle: { type: "SONGS", fn: (s) => [s.title, ...s.altTitles] },
|
||||
bmsChartHash: {
|
||||
type: "CHARTS",
|
||||
fn: (c: ChartDocument<"bms:7K" | "bms:14K">) => [c.data.hashMD5, c.data.hashSHA256],
|
||||
},
|
||||
inGameID: { type: "CHARTS", fn: (c) => `${c.data.inGameID}-${c.difficulty}` },
|
||||
itgChartHash: { type: "CHARTS", fn: (c) => c.data.hashGSV3 },
|
||||
popnChartHash: { type: "CHARTS", fn: (c) => c.data.hashSHA256 },
|
||||
sdvxInGameID: {
|
||||
type: "CHARTS",
|
||||
fn: (c) => {
|
||||
let diff = c.difficulty;
|
||||
|
||||
if (["XCD", "HVN", "GRV", "VVD", "INF"].includes(diff)) {
|
||||
diff = "ANY_INF";
|
||||
}
|
||||
|
||||
return `${c.data.inGameID}-${diff}`;
|
||||
},
|
||||
},
|
||||
uscChartHash: { type: "CHARTS", fn: (c) => c.data.hashSHA1 },
|
||||
};
|
||||
|
||||
let exitCode = 0;
|
||||
const suites: Array<{ name: string; good: boolean; report: unknown }> = [];
|
||||
|
||||
for (const { game, matchType, playtype } of uniquenessChecks) {
|
||||
const name = `${FormatGame(game, playtype)} ${matchType}`;
|
||||
console.log(`[CHECKING MATCHTYPE] ${name}.`);
|
||||
|
||||
const handler = MATCH_TYPE_CHECKS[matchType];
|
||||
|
||||
let success = 0;
|
||||
let fails = 0;
|
||||
|
||||
const data =
|
||||
handler.type === "CHARTS"
|
||||
? ReadCollection(`charts-${game}.json`)
|
||||
: ReadCollection(`songs-${game}.json`);
|
||||
|
||||
const uniqueIDs = new Set();
|
||||
for (const el of data.filter((e) => e.playtype === playtype)) {
|
||||
// skip non-primaries as they can't really be matched anyway.
|
||||
if (handler.type === "CHARTS" && !el.isPrimary) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let newUniqueThingies = handler.fn(el);
|
||||
|
||||
// make single returns into arrays. convenient.
|
||||
if (!Array.isArray(newUniqueThingies)) {
|
||||
newUniqueThingies = [newUniqueThingies];
|
||||
}
|
||||
|
||||
for (const maybeUnique of newUniqueThingies) {
|
||||
if (uniqueIDs.has(maybeUnique)) {
|
||||
console.log(
|
||||
chalk.red(
|
||||
`ID ${maybeUnique} wasn't unique in ${FormatGame(
|
||||
game,
|
||||
playtype
|
||||
)} (matchType=${matchType}). It needs to be for this matchType to be legal.`
|
||||
)
|
||||
);
|
||||
fails++;
|
||||
} else {
|
||||
success++;
|
||||
uniqueIDs.add(maybeUnique);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const report = `GOOD: ${success}, BAD: ${fails}(${Math.min(
|
||||
(success * 100) / fails,
|
||||
100
|
||||
).toFixed(2)}%)`;
|
||||
if (fails > 0) {
|
||||
console.error(chalk.red(`[FAILED] ${name}. ${report}.`));
|
||||
exitCode++;
|
||||
} else {
|
||||
console.log(chalk.green(`[GOOD] ${name}. ${report}.`));
|
||||
}
|
||||
|
||||
suites.push({ name, report, good: fails === 0 });
|
||||
}
|
||||
|
||||
console.log(`=== Suite Overview ===`);
|
||||
for (const suite of suites) {
|
||||
console.log(chalk[suite.good ? "green" : "red"](`[MATCHTYPES] ${suite.name}: ${suite.report}`));
|
||||
}
|
||||
|
||||
process.exit(exitCode);
|
||||
@@ -25,7 +25,7 @@ appropriate way to send files.
|
||||
|
||||
| HTTP Header | Description |
|
||||
| :: | :: |
|
||||
| `X-User-Intent` (optional) | If this header is present, the request is assumed to have been sent with 'User Intent'. For more on this, see [Import Types](../../tachi-server/import/import-types.md) |
|
||||
| `X-User-Intent` (optional) | If this header is "true", the request is assumed to have been sent with 'User Intent'. For more on this, see [Import Types](../../tachi-server/import/import-types.md) |
|
||||
|
||||
!!! info
|
||||
It's the responsibility of the API user to use the X-User-Intent
|
||||
|
||||
@@ -20,7 +20,7 @@ aren't an error.
|
||||
When this is thrown, the score is skipped, and nothing is
|
||||
imported.
|
||||
|
||||
## KTDataNotFoundFailure
|
||||
## SongOrChartNotFoundFailure
|
||||
|
||||
!!! bug
|
||||
KTData means 'Kamaitachi Data', but this applies to
|
||||
|
||||
@@ -28,7 +28,7 @@ As a rough outline:
|
||||
// whether this import worked or not
|
||||
success: boolean;
|
||||
// ScoreImported implies success: true, and all others imply success: false.
|
||||
type: "ScoreImported" | "KTDataNotFound" // ... and more;
|
||||
type: "ScoreImported" | "SongOrChartNotFound" // ... and more;
|
||||
// An error message.
|
||||
message: String | null;
|
||||
// Some errors return some data about the error, or things like scoreImported returns the score that was imported.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Orphan Scores
|
||||
|
||||
KTDataNotFoundFailures state that we don't have the data
|
||||
SongOrChartNotFoundFailures state that we don't have the data
|
||||
to understand what chart/song this score is for.
|
||||
|
||||
However, that doesn't necessarily imply we won't have that
|
||||
@@ -19,7 +19,7 @@ Orphaned scores store the `data` and `context` from the parser.
|
||||
When unorphaning is attempted, the Converter Function is
|
||||
called with that data and context.
|
||||
|
||||
If it results in a KTDataNotFoundFailure, we do nothing.
|
||||
If it results in a SongOrChartNotFoundFailure, we do nothing.
|
||||
|
||||
If it results in a success, we have a new DryScore we can import.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import type { ImportTypeContextMap, ImportTypeDataMap } from "../../import-types/common/types";
|
||||
import type { ImportTypes } from "tachi-common";
|
||||
|
||||
export type FailureTypes = "Internal" | "InvalidScore" | "KTDataNotFound" | "SkipScore";
|
||||
export type FailureTypes = "Internal" | "InvalidScore" | "SongOrChartNotFound" | "SkipScore";
|
||||
|
||||
export class ConverterFailure extends Error {
|
||||
message: string;
|
||||
@@ -35,11 +35,11 @@ export class SkipScoreFailure extends ConverterFailure {
|
||||
}
|
||||
|
||||
/**
|
||||
* KTDataNotFoundError - We could not find the song or chart this score
|
||||
* SongOrChartNotFoundError - We could not find the song or chart this score
|
||||
* belongs to. The identifier used to try and match this chart is stored here.
|
||||
* Alongside any other fields used in the query.
|
||||
*/
|
||||
export class KTDataNotFoundFailure<T extends ImportTypes> extends ConverterFailure {
|
||||
export class SongOrChartNotFoundFailure<T extends ImportTypes> extends ConverterFailure {
|
||||
data: ImportTypeDataMap[T];
|
||||
converterContext: ImportTypeContextMap[T];
|
||||
importType: T;
|
||||
@@ -50,7 +50,7 @@ export class KTDataNotFoundFailure<T extends ImportTypes> extends ConverterFailu
|
||||
data: ImportTypeDataMap[T],
|
||||
context: ImportTypeContextMap[T]
|
||||
) {
|
||||
super(message, "KTDataNotFound");
|
||||
super(message, "SongOrChartNotFound");
|
||||
|
||||
this.importType = importType;
|
||||
this.data = data;
|
||||
|
||||
@@ -111,7 +111,7 @@ export async function ReprocessOrphan(
|
||||
|
||||
if ("failureType" in res) {
|
||||
// If the data still can't be found, we do nothing about it.
|
||||
if (res.failureType === "KTDataNotFound") {
|
||||
if (res.failureType === "SongOrChartNotFound") {
|
||||
logger.debug(`Unorphaning ${orphan.orphanID} failed. (${res.message})`);
|
||||
return false;
|
||||
} else if (res.failureType === "Internal") {
|
||||
|
||||
@@ -7,7 +7,7 @@ import db from "external/mongo/db";
|
||||
import { AppendLogCtx } from "lib/logger/logger";
|
||||
import { ClassToObject } from "utils/misc";
|
||||
import type { ConverterFnSuccessReturn, ConverterFunction } from "../../import-types/common/types";
|
||||
import type { ConverterFailure, KTDataNotFoundFailure } from "../common/converter-failures";
|
||||
import type { ConverterFailure, SongOrChartNotFoundFailure } from "../common/converter-failures";
|
||||
import type { DryScore } from "../common/types";
|
||||
import type { KtLogger } from "lib/logger/logger";
|
||||
import type { ScoreImportJob } from "lib/score-import/worker/types";
|
||||
@@ -147,10 +147,10 @@ export async function ImportIterableDatapoint<D, C>(
|
||||
// questionable bugs with respect to maintaining prototype chains.
|
||||
// Even though it's uglier, we instead use a stringly-typed union.
|
||||
switch (err.failureType) {
|
||||
case "KTDataNotFound": {
|
||||
const dnfErr = err as KTDataNotFoundFailure<ImportTypes>;
|
||||
case "SongOrChartNotFound": {
|
||||
const dnfErr = err as SongOrChartNotFoundFailure<ImportTypes>;
|
||||
|
||||
logger.info(`KTDataNotFoundFailure: ${dnfErr.message}`, {
|
||||
logger.info(`SongOrChartNotFoundFailure: ${dnfErr.message}`, {
|
||||
err: ClassToObject(dnfErr),
|
||||
hideFromConsole: ["cfnReturn"],
|
||||
});
|
||||
@@ -173,7 +173,7 @@ export async function ImportIterableDatapoint<D, C>(
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
type: "KTDataNotFound",
|
||||
type: "SongOrChartNotFound",
|
||||
message: dnfErr.message,
|
||||
content: {
|
||||
context: dnfErr.converterContext,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FormatCGService } from "../util";
|
||||
import {
|
||||
InternalFailure,
|
||||
InvalidScoreFailure,
|
||||
KTDataNotFoundFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "lib/score-import/framework/common/converter-failures";
|
||||
import {
|
||||
GenericGetGradeAndPercent,
|
||||
@@ -34,7 +34,7 @@ export const ConverterAPICGMuseca: ConverterFunction<CGMusecaScore, CGContext> =
|
||||
);
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Could not find chart with songID ${data.internalId} (${difficulty} - Version ${version})`,
|
||||
importType,
|
||||
data,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FormatCGService } from "../util";
|
||||
import {
|
||||
InternalFailure,
|
||||
InvalidScoreFailure,
|
||||
KTDataNotFoundFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "lib/score-import/framework/common/converter-failures";
|
||||
import {
|
||||
GenericGetGradeAndPercent,
|
||||
@@ -39,7 +39,7 @@ export const ConverterAPICGPopn: ConverterFunction<CGPopnScore, CGContext> = asy
|
||||
);
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Could not find chart with songID ${data.internalId} (${difficulty} - Version ${version})`,
|
||||
importType,
|
||||
data,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FormatCGService } from "../util";
|
||||
import {
|
||||
InternalFailure,
|
||||
InvalidScoreFailure,
|
||||
KTDataNotFoundFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "lib/score-import/framework/common/converter-failures";
|
||||
import {
|
||||
GenericGetGradeAndPercent,
|
||||
@@ -27,7 +27,7 @@ export const ConverterAPICGSDVX: ConverterFunction<CGSDVXScore, CGContext> = asy
|
||||
const chart = await FindSDVXChartOnInGameIDVersion(data.internalId, difficulty, version);
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Could not find chart with songID ${data.internalId} (${difficulty} - Version ${version})`,
|
||||
importType,
|
||||
data,
|
||||
|
||||
@@ -105,7 +105,7 @@ t.test("#ConvertAPIKaiIIDX", (t) => {
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should throw KTDataNotFound on unknown chart", (t) => {
|
||||
t.test("Should throw SongOrChartNotFound on unknown chart", (t) => {
|
||||
t.rejects(
|
||||
() =>
|
||||
ConvertAPIKaiIIDX(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
InternalFailure,
|
||||
InvalidScoreFailure,
|
||||
KTDataNotFoundFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "../../../../framework/common/converter-failures";
|
||||
import {
|
||||
GenericGetGradeAndPercent,
|
||||
@@ -235,7 +235,7 @@ export const ConvertAPIKaiIIDX: ConverterFunction<unknown, KaiContext> = async (
|
||||
);
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Could not find chart with songID ${musicID} (${playtype} ${score.difficulty} - Version ${score.version_played})`,
|
||||
importType,
|
||||
data,
|
||||
|
||||
@@ -70,7 +70,7 @@ t.test("#ConvertAPIKaiSDVX", (t) => {
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should throw KTDataNotFound on unknown chart", (t) => {
|
||||
t.test("Should throw SongOrChartNotFound on unknown chart", (t) => {
|
||||
t.rejects(
|
||||
() =>
|
||||
ConvertAPIKaiSDVX(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
InternalFailure,
|
||||
InvalidScoreFailure,
|
||||
KTDataNotFoundFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "../../../../framework/common/converter-failures";
|
||||
import {
|
||||
GenericGetGradeAndPercent,
|
||||
@@ -53,7 +53,7 @@ export const ConvertAPIKaiSDVX: ConverterFunction<unknown, KaiContext> = async (
|
||||
const chart = await FindSDVXChartOnInGameIDVersion(score.music_id, difficulty, version);
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Could not find chart with songID ${score.music_id} (${difficulty} - Version ${version})`,
|
||||
importType,
|
||||
data,
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { ConverterBatchManual, ResolveChartFromSong, ResolveMatchTypeToKTData } from "./converter";
|
||||
import {
|
||||
ConverterBatchManual,
|
||||
ResolveChartFromSong,
|
||||
ResolveMatchTypeToTachiData,
|
||||
} from "./converter";
|
||||
import { InvalidScoreFailure } from "../../../framework/common/converter-failures";
|
||||
import deepmerge from "deepmerge";
|
||||
import db from "external/mongo/db";
|
||||
@@ -46,11 +50,11 @@ const logger = CreateLogCtx(__filename);
|
||||
|
||||
const importType = "file/batch-manual" as const;
|
||||
|
||||
t.test("#ResolveMatchTypeToKTData", (t) => {
|
||||
t.test("#ResolveMatchTypeToTachiData", (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
t.test("Should resolve for the songID if the matchType is songID", async (t) => {
|
||||
const res = await ResolveMatchTypeToKTData(
|
||||
const res = await ResolveMatchTypeToTachiData(
|
||||
baseBatchManualScore,
|
||||
context,
|
||||
importType,
|
||||
@@ -65,7 +69,7 @@ t.test("#ResolveMatchTypeToKTData", (t) => {
|
||||
|
||||
t.rejects(
|
||||
() =>
|
||||
ResolveMatchTypeToKTData(
|
||||
ResolveMatchTypeToTachiData(
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-expect-error bad
|
||||
deepmerge(baseBatchManualScore, { identifier: "90000" }),
|
||||
@@ -80,7 +84,7 @@ t.test("#ResolveMatchTypeToKTData", (t) => {
|
||||
});
|
||||
|
||||
t.test("Should resolve for the song title if the matchType is songTitle", async (t) => {
|
||||
const res = await ResolveMatchTypeToKTData(
|
||||
const res = await ResolveMatchTypeToTachiData(
|
||||
deepmerge(baseBatchManualScore, { matchType: "songTitle", identifier: "5.1.1." }),
|
||||
context,
|
||||
importType,
|
||||
@@ -95,7 +99,7 @@ t.test("#ResolveMatchTypeToKTData", (t) => {
|
||||
|
||||
t.rejects(
|
||||
() =>
|
||||
ResolveMatchTypeToKTData(
|
||||
ResolveMatchTypeToTachiData(
|
||||
deepmerge(baseBatchManualScore, {
|
||||
matchType: "songTitle",
|
||||
identifier: "INVALID_TITLE",
|
||||
@@ -111,7 +115,7 @@ t.test("#ResolveMatchTypeToKTData", (t) => {
|
||||
});
|
||||
|
||||
t.test("Should resolve for the sdvx inGameID if matchType is sdvxInGameID", async (t) => {
|
||||
const res = await ResolveMatchTypeToKTData(
|
||||
const res = await ResolveMatchTypeToTachiData(
|
||||
{
|
||||
matchType: "sdvxInGameID",
|
||||
identifier: "1",
|
||||
@@ -132,7 +136,7 @@ t.test("#ResolveMatchTypeToKTData", (t) => {
|
||||
|
||||
t.rejects(
|
||||
() =>
|
||||
ResolveMatchTypeToKTData(
|
||||
ResolveMatchTypeToTachiData(
|
||||
{
|
||||
matchType: "sdvxInGameID",
|
||||
identifier: "9999999",
|
||||
@@ -159,7 +163,7 @@ t.test("#ResolveMatchTypeToKTData", (t) => {
|
||||
} as ChartDocument<"sdvx:Single">)
|
||||
);
|
||||
|
||||
const res = await ResolveMatchTypeToKTData(
|
||||
const res = await ResolveMatchTypeToTachiData(
|
||||
{
|
||||
matchType: "sdvxInGameID",
|
||||
identifier: "1",
|
||||
@@ -187,7 +191,7 @@ t.test("#ResolveMatchTypeToKTData", (t) => {
|
||||
|
||||
const bmsContext: BatchManualContext = deepmerge(context, { game: "bms", playtype: "7K" });
|
||||
|
||||
const resMD5 = await ResolveMatchTypeToKTData(
|
||||
const resMD5 = await ResolveMatchTypeToTachiData(
|
||||
deepmerge(baseBatchManualScore, {
|
||||
matchType: "bmsChartHash",
|
||||
identifier: GAZER17MD5,
|
||||
@@ -203,7 +207,7 @@ t.test("#ResolveMatchTypeToKTData", (t) => {
|
||||
"Should return the right song and chart."
|
||||
);
|
||||
|
||||
const resSHA256 = await ResolveMatchTypeToKTData(
|
||||
const resSHA256 = await ResolveMatchTypeToTachiData(
|
||||
deepmerge(baseBatchManualScore, {
|
||||
matchType: "bmsChartHash",
|
||||
identifier: GAZER17SHA256,
|
||||
@@ -221,7 +225,7 @@ t.test("#ResolveMatchTypeToKTData", (t) => {
|
||||
|
||||
t.rejects(
|
||||
() =>
|
||||
ResolveMatchTypeToKTData(
|
||||
ResolveMatchTypeToTachiData(
|
||||
deepmerge(baseBatchManualScore, {
|
||||
matchType: "bmsChartHash",
|
||||
identifier: "bad_hash",
|
||||
@@ -244,7 +248,7 @@ t.test("#ResolveMatchTypeToKTData", (t) => {
|
||||
playtype: "9B",
|
||||
});
|
||||
|
||||
const res = await ResolveMatchTypeToKTData(
|
||||
const res = await ResolveMatchTypeToTachiData(
|
||||
deepmerge(baseBatchManualScore, {
|
||||
matchType: "popnChartHash",
|
||||
identifier: chartHash,
|
||||
@@ -274,7 +278,7 @@ t.test("#ResolveMatchTypeToKTData", (t) => {
|
||||
const chartHash = "2c26d666fa7c907e85115dbb279c267c14a263d47b2d46a93f99eae49d779119";
|
||||
|
||||
t.rejects(() =>
|
||||
ResolveMatchTypeToKTData(
|
||||
ResolveMatchTypeToTachiData(
|
||||
deepmerge(baseBatchManualScore, {
|
||||
matchType: "popnChartHash",
|
||||
identifier: chartHash,
|
||||
@@ -296,7 +300,7 @@ t.test("#ResolveMatchTypeToKTData", (t) => {
|
||||
playtype: "Controller",
|
||||
});
|
||||
|
||||
const res = await ResolveMatchTypeToKTData(
|
||||
const res = await ResolveMatchTypeToTachiData(
|
||||
deepmerge(baseBatchManualScore, {
|
||||
matchType: "uscChartHash",
|
||||
identifier: chartHash,
|
||||
@@ -328,7 +332,7 @@ t.test("#ResolveMatchTypeToKTData", (t) => {
|
||||
|
||||
t.rejects(
|
||||
() =>
|
||||
ResolveMatchTypeToKTData(
|
||||
ResolveMatchTypeToTachiData(
|
||||
deepmerge(baseBatchManualScore, {
|
||||
matchType: "uscChartHash",
|
||||
identifier: chartHash,
|
||||
@@ -346,7 +350,7 @@ t.test("#ResolveMatchTypeToKTData", (t) => {
|
||||
t.test("Should trigger failsave if invalid matchType is provided.", (t) => {
|
||||
t.rejects(
|
||||
() =>
|
||||
ResolveMatchTypeToKTData(
|
||||
ResolveMatchTypeToTachiData(
|
||||
deepmerge(baseBatchManualScore, {
|
||||
matchType: "BAD_MATCHTYPE",
|
||||
}),
|
||||
@@ -354,7 +358,7 @@ t.test("#ResolveMatchTypeToKTData", (t) => {
|
||||
importType,
|
||||
logger
|
||||
),
|
||||
new InvalidScoreFailure(`Invalid matchType BAD_MATCHTYPE`)
|
||||
new InvalidScoreFailure(`Cannot use matchType BAD_MATCHTYPE for beatmania IIDX (SP)`)
|
||||
);
|
||||
|
||||
t.end();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
InternalFailure,
|
||||
InvalidScoreFailure,
|
||||
KTDataNotFoundFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "../../../framework/common/converter-failures";
|
||||
import { GenericGetGradeAndPercent, JubeatGetGrade } from "../../../framework/common/score-utils";
|
||||
import {
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
AssertStrAsPositiveInt,
|
||||
} from "../../../framework/common/string-asserts";
|
||||
import db from "external/mongo/db";
|
||||
import { GetGamePTConfig } from "tachi-common";
|
||||
import { FormatGame, GetGamePTConfig } from "tachi-common";
|
||||
import { FloorToNDP } from "utils/misc";
|
||||
import {
|
||||
FindBMSChartOnHash,
|
||||
@@ -49,7 +49,7 @@ export const ConverterBatchManual: ConverterFunction<BatchManualScore, BatchManu
|
||||
) => {
|
||||
const game = context.game;
|
||||
|
||||
const { song, chart } = await ResolveMatchTypeToKTData(data, context, importType, logger);
|
||||
const { song, chart } = await ResolveMatchTypeToTachiData(data, context, importType, logger);
|
||||
|
||||
// yet another temporary hack, jubeat's percent is not a function of score,
|
||||
// it's actually an entirely separate metric. We need to support this, so we'll
|
||||
@@ -139,24 +139,38 @@ export const ConverterBatchManual: ConverterFunction<BatchManualScore, BatchManu
|
||||
};
|
||||
};
|
||||
|
||||
export async function ResolveMatchTypeToKTData(
|
||||
export async function ResolveMatchTypeToTachiData(
|
||||
data: BatchManualScore,
|
||||
context: BatchManualContext,
|
||||
importType: ImportTypes,
|
||||
logger: KtLogger
|
||||
): Promise<{ song: SongDocument; chart: ChartDocument }> {
|
||||
const game = context.game;
|
||||
const { game, playtype } = context;
|
||||
|
||||
const config = GetGamePTConfig(game, playtype);
|
||||
|
||||
if (!config.supportedMatchTypes.includes(data.matchType)) {
|
||||
// special, more helpful error message
|
||||
if (game === "sdvx" && data.matchType === "inGameID") {
|
||||
throw new InvalidScoreFailure(
|
||||
`Cannot use matchType ${data.matchType} for ${FormatGame(
|
||||
game,
|
||||
playtype
|
||||
)}. Use 'sdvxInGameID' instead.`
|
||||
);
|
||||
}
|
||||
|
||||
throw new InvalidScoreFailure(
|
||||
`Cannot use matchType ${data.matchType} for ${FormatGame(game, playtype)}`
|
||||
);
|
||||
}
|
||||
|
||||
switch (data.matchType) {
|
||||
case "bmsChartHash": {
|
||||
if (game !== "bms") {
|
||||
throw new InvalidScoreFailure(`Cannot use bmsChartHash lookup on ${game}.`);
|
||||
}
|
||||
|
||||
const chart = await FindBMSChartOnHash(data.identifier);
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Cannot find chart for hash ${data.identifier}.`,
|
||||
importType,
|
||||
data,
|
||||
@@ -183,14 +197,10 @@ export async function ResolveMatchTypeToKTData(
|
||||
}
|
||||
|
||||
case "itgChartHash": {
|
||||
if (game !== "itg") {
|
||||
throw new InvalidScoreFailure(`Cannot use itgChartHash lookup on ${game}.`);
|
||||
}
|
||||
|
||||
const chart = await FindITGChartOnHash(data.identifier);
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Cannot find chart for itgChartHash ${data.identifier}.`,
|
||||
importType,
|
||||
data,
|
||||
@@ -211,17 +221,13 @@ export async function ResolveMatchTypeToKTData(
|
||||
}
|
||||
|
||||
case "popnChartHash": {
|
||||
if (game !== "popn") {
|
||||
throw new InvalidScoreFailure(`Cannot use popnChartHash lookup on ${game}.`);
|
||||
}
|
||||
|
||||
const chart = await db.charts.popn.findOne({
|
||||
playtype: context.playtype,
|
||||
"data.hashSHA256": data.identifier,
|
||||
});
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Cannot find chart for popnChartHash ${data.identifier} (${context.playtype}).`,
|
||||
importType,
|
||||
data,
|
||||
@@ -250,7 +256,7 @@ export async function ResolveMatchTypeToKTData(
|
||||
const song = await FindSongOnID(game, songID);
|
||||
|
||||
if (!song) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Cannot find song with songID ${data.identifier}.`,
|
||||
importType,
|
||||
data,
|
||||
@@ -267,7 +273,7 @@ export async function ResolveMatchTypeToKTData(
|
||||
const song = await FindSongOnTitleInsensitive(game, data.identifier);
|
||||
|
||||
if (!song) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Cannot find song with title ${data.identifier}.`,
|
||||
importType,
|
||||
data,
|
||||
@@ -281,12 +287,6 @@ export async function ResolveMatchTypeToKTData(
|
||||
}
|
||||
|
||||
case "sdvxInGameID": {
|
||||
if (game !== "sdvx") {
|
||||
throw new InvalidScoreFailure(
|
||||
`A matchType of sdvxInGameID is only supported by SDVX.`
|
||||
);
|
||||
}
|
||||
|
||||
let chart: ChartDocument | null;
|
||||
|
||||
const identifier = Number(data.identifier);
|
||||
@@ -322,7 +322,7 @@ export async function ResolveMatchTypeToKTData(
|
||||
}
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Cannot find SDVX chart with inGameID ${identifier}, difficulty ${diff} and version ${context.version}.`,
|
||||
importType,
|
||||
data,
|
||||
@@ -341,27 +341,6 @@ export async function ResolveMatchTypeToKTData(
|
||||
}
|
||||
|
||||
case "inGameID": {
|
||||
const gamesWithInGameIDSupport = [
|
||||
"iidx",
|
||||
"popn",
|
||||
"jubeat",
|
||||
"chunithm",
|
||||
"gitadora",
|
||||
"museca",
|
||||
];
|
||||
|
||||
if (game === "sdvx") {
|
||||
throw new InvalidScoreFailure(
|
||||
`Cannot use inGameID as a matchType for SDVX. Use matchType: 'sdvxInGameID' instead.`
|
||||
);
|
||||
}
|
||||
|
||||
if (!gamesWithInGameIDSupport.includes(game)) {
|
||||
throw new InvalidScoreFailure(
|
||||
`Cannot use inGameID on game ${game}. The game may not have a concept of an in game ID, or support just might not exist yet.`
|
||||
);
|
||||
}
|
||||
|
||||
const identifier = Number(data.identifier);
|
||||
|
||||
const difficulty = AssertStrAsDifficulty(data.difficulty, game, context.playtype);
|
||||
@@ -384,7 +363,7 @@ export async function ResolveMatchTypeToKTData(
|
||||
}
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Cannot find chart for inGameID ${data.identifier} (${context.playtype}).`,
|
||||
importType,
|
||||
data,
|
||||
@@ -413,7 +392,7 @@ export async function ResolveMatchTypeToKTData(
|
||||
});
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Cannot find chart with hash ${data.identifier}.`,
|
||||
importType,
|
||||
data,
|
||||
@@ -475,7 +454,7 @@ export async function ResolveChartFromSong(
|
||||
}
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Cannot find chart for ${song.title} (${context.playtype} ${difficulty})`,
|
||||
importType,
|
||||
data,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
InvalidScoreFailure,
|
||||
KTDataNotFoundFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
SkipScoreFailure,
|
||||
} from "../../../framework/common/converter-failures";
|
||||
import {
|
||||
@@ -62,7 +62,7 @@ const ConvertEamIIDXCSV: ConverterFunction<
|
||||
const tachiSong = await FindSongOnTitle("iidx", data.title);
|
||||
|
||||
if (!tachiSong) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Could not find song for ${data.title}.`,
|
||||
importType,
|
||||
data,
|
||||
@@ -85,7 +85,7 @@ const ConvertEamIIDXCSV: ConverterFunction<
|
||||
)) as ChartDocument<"iidx:DP" | "iidx:SP"> | null;
|
||||
|
||||
if (!tachiChart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Could not find chart for ${HUMANISED_CHART_TITLE}`,
|
||||
"file/eamusement-iidx-csv",
|
||||
data,
|
||||
|
||||
@@ -90,7 +90,7 @@ t.test("#ConvertEamSDVXCSV", (t) => {
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should throw KTDataNotFound on unknown song", (t) => {
|
||||
t.test("Should throw SongOrChartNotFound on unknown song", (t) => {
|
||||
t.rejects(() => conv({ title: "INVALID SONG" }), {
|
||||
message: /Could not find song for INVALID SONG\./u,
|
||||
});
|
||||
@@ -98,7 +98,7 @@ t.test("#ConvertEamSDVXCSV", (t) => {
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should throw KTDataNotFound on unknown chart", (t) => {
|
||||
t.test("Should throw SongOrChartNotFound on unknown chart", (t) => {
|
||||
t.rejects(() => conv({ difficulty: "VIVID" }), {
|
||||
message: /Could not find chart for ALBIDA Powerless Mix \[VVD\]\./u,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
InvalidScoreFailure,
|
||||
KTDataNotFoundFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "lib/score-import/framework/common/converter-failures";
|
||||
import { GenericGetGradeAndPercent } from "lib/score-import/framework/common/score-utils";
|
||||
import { AssertStrAsPositiveInt } from "lib/score-import/framework/common/string-asserts";
|
||||
@@ -41,7 +41,7 @@ const ConvertEamSDVXCSV: ConverterFunction<SDVXEamusementCSVData, EmptyObject> =
|
||||
const song = await FindSongOnTitle("sdvx", data.title);
|
||||
|
||||
if (!song) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Could not find song for ${data.title}.`,
|
||||
importType,
|
||||
data,
|
||||
@@ -61,7 +61,7 @@ const ConvertEamSDVXCSV: ConverterFunction<SDVXEamusementCSVData, EmptyObject> =
|
||||
const chart = await FindChartWithPTDF("sdvx", song.id, "Single", difficulty);
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Could not find chart for ${humanisedChartTitle}.`,
|
||||
importType,
|
||||
data,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
InternalFailure,
|
||||
KTDataNotFoundFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "../../../framework/common/converter-failures";
|
||||
import {
|
||||
GenericGetGradeAndPercent,
|
||||
@@ -33,7 +33,7 @@ export const ConvertFileMerIIDX: ConverterFunction<MerScore, EmptyObject> = asyn
|
||||
const chart = await FindIIDXChartOnInGameID(data.music_id, playtype, data.diff_type);
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Could not find chart with musicID ${data.music_id} (${playtype} ${data.diff_type}.)`,
|
||||
importType,
|
||||
data,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
InvalidScoreFailure,
|
||||
KTDataNotFoundFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
SkipScoreFailure,
|
||||
} from "../../../framework/common/converter-failures";
|
||||
import {
|
||||
@@ -117,7 +117,7 @@ export const ConvertFileS3: ConverterFunction<S3Score, EmptyObject> = async (
|
||||
const song = await FindSongOnTitleInsensitive("iidx", data.songname);
|
||||
|
||||
if (!song) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Could not find song with title ${data.songname}`,
|
||||
importType,
|
||||
data,
|
||||
@@ -131,7 +131,7 @@ export const ConvertFileS3: ConverterFunction<S3Score, EmptyObject> = async (
|
||||
const chart = await FindChartWithPTDFVersion("iidx", song.id, playtype, difficulty, version);
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Could not find chart ${data.songname} (${playtype} ${difficulty} version (${version}))`,
|
||||
importType,
|
||||
data,
|
||||
|
||||
@@ -129,7 +129,7 @@ t.test("#ConverterIRBarbatos", (t) => {
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should throw KTDataNotFound if chart not found.", (t) => {
|
||||
t.test("Should throw SongOrChartNotFound if chart not found.", (t) => {
|
||||
t.rejects(
|
||||
() =>
|
||||
ConverterIRBarbatos(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
InternalFailure,
|
||||
KTDataNotFoundFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "../../../framework/common/converter-failures";
|
||||
import { GenericGetGradeAndPercent } from "../../../framework/common/score-utils";
|
||||
import { FindSDVXChartOnInGameIDVersion } from "utils/queries/charts";
|
||||
@@ -42,7 +42,7 @@ export const ConverterIRBarbatos: ConverterFunction<
|
||||
const chart = await FindSDVXChartOnInGameIDVersion(data.song_id, difficulty, context.version);
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Could not find chart with songID ${data.song_id} (${difficulty})`,
|
||||
importType,
|
||||
data,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
InternalFailure,
|
||||
InvalidScoreFailure,
|
||||
KTDataNotFoundFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "../../../framework/common/converter-failures";
|
||||
import { GenericGetGradeAndPercent } from "../../../framework/common/score-utils";
|
||||
import db from "external/mongo/db";
|
||||
@@ -108,7 +108,7 @@ async function HandleOrphanChartProcess(
|
||||
// If chart wasn't unorphaned as a result of this request
|
||||
// orphan this score and return ktdnf
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`This chart (${context.chart.artist} - ${context.chart.title}) is orphaned.`,
|
||||
"ir/beatoraja",
|
||||
data,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
InternalFailure,
|
||||
KTDataNotFoundFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "../../../framework/common/converter-failures";
|
||||
import { GenericGetGradeAndPercent } from "../../../framework/common/score-utils";
|
||||
import { FERVIDEX_LAMP_LOOKUP, SplitFervidexChartRef } from "../fervidex/converter";
|
||||
@@ -26,7 +26,7 @@ export const ConverterIRFervidexStatic: ConverterFunction<
|
||||
);
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Could not find chart with songID ${data.song_id} (${playtype} ${difficulty} Version ${context.version})`,
|
||||
importType,
|
||||
data,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
InternalFailure,
|
||||
InvalidScoreFailure,
|
||||
KTDataNotFoundFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "../../../framework/common/converter-failures";
|
||||
import { GenericGetGradeAndPercent } from "../../../framework/common/score-utils";
|
||||
import { IsNullishOrEmptyStr } from "utils/misc";
|
||||
@@ -164,7 +164,7 @@ export const ConverterIRFervidex: ConverterFunction<FervidexScore, FervidexConte
|
||||
}
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Could not find chart with songID ${data.entry_id} (${playtype} ${difficulty} [${context.version}])`,
|
||||
importType,
|
||||
data,
|
||||
|
||||
@@ -57,7 +57,7 @@ t.test("#ConverterKsHookSV6CStatic", (t) => {
|
||||
"ir/kshook-sv6c",
|
||||
logger
|
||||
),
|
||||
"Should throw a KTDataNotFoundError if chart can't be found."
|
||||
"Should throw a SongOrChartNotFoundError if chart can't be found."
|
||||
);
|
||||
|
||||
t.end();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SV6CConvertDifficulty, SV6CConvertLamp } from "../kshook-sv6c/converter";
|
||||
import {
|
||||
InternalFailure,
|
||||
KTDataNotFoundFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "lib/score-import/framework/common/converter-failures";
|
||||
import { GenericGetGradeAndPercent } from "lib/score-import/framework/common/score-utils";
|
||||
import { FindSDVXChartOnInGameIDVersion } from "utils/queries/charts";
|
||||
@@ -20,7 +20,7 @@ export const ConverterKsHookSV6CStatic: ConverterFunction<
|
||||
const chart = await FindSDVXChartOnInGameIDVersion(data.music_id, diff, "konaste");
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Could not find chart with songID ${data.music_id} (${diff} for Konaste).`,
|
||||
importType,
|
||||
data,
|
||||
|
||||
@@ -60,7 +60,7 @@ t.test("#ConverterIRKsHookSV6C", (t) => {
|
||||
"ir/kshook-sv6c",
|
||||
logger
|
||||
),
|
||||
"Should throw a KTDataNotFoundError if chart can't be found."
|
||||
"Should throw a SongOrChartNotFoundError if chart can't be found."
|
||||
);
|
||||
|
||||
t.end();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
InternalFailure,
|
||||
KTDataNotFoundFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "lib/score-import/framework/common/converter-failures";
|
||||
import { GenericGetGradeAndPercent } from "lib/score-import/framework/common/score-utils";
|
||||
import { FindSDVXChartOnInGameIDVersion } from "utils/queries/charts";
|
||||
@@ -21,7 +21,7 @@ export const ConverterIRKsHookSV6C: ConverterFunction<KsHookSV6CScore, KsHookSV6
|
||||
const chart = await FindSDVXChartOnInGameIDVersion(data.music_id, diff, "konaste");
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Could not find chart with songID ${data.music_id} (${diff} for Konaste).`,
|
||||
importType,
|
||||
data,
|
||||
|
||||
@@ -95,7 +95,7 @@ t.test("#ConverterLR2Hook", (t) => {
|
||||
"ir/lr2hook",
|
||||
logger
|
||||
),
|
||||
"Should throw a KTDataNotFoundError if chart can't be found."
|
||||
"Should throw a SongOrChartNotFoundError if chart can't be found."
|
||||
);
|
||||
|
||||
t.end();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
InternalFailure,
|
||||
KTDataNotFoundFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "lib/score-import/framework/common/converter-failures";
|
||||
import { GenericGetGradeAndPercent } from "lib/score-import/framework/common/score-utils";
|
||||
import { FindBMSChartOnHash } from "utils/queries/charts";
|
||||
@@ -19,7 +19,7 @@ export const ConverterLR2Hook: ConverterFunction<LR2HookScore, LR2HookContext> =
|
||||
const chart = await FindBMSChartOnHash(data.md5);
|
||||
|
||||
if (!chart) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Could not find chart with md5 ${data.md5}.`,
|
||||
importType,
|
||||
data,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
InternalFailure,
|
||||
InvalidScoreFailure,
|
||||
KTDataNotFoundFailure,
|
||||
SongOrChartNotFoundFailure,
|
||||
} from "../../../framework/common/converter-failures";
|
||||
import { GenericGetGradeAndPercent } from "../../../framework/common/score-utils";
|
||||
import db from "external/mongo/db";
|
||||
@@ -100,7 +100,7 @@ export const ConverterIRUSC: ConverterFunction<USCClientScore, IRUSCContext> = a
|
||||
});
|
||||
|
||||
if (!chartDoc) {
|
||||
throw new KTDataNotFoundFailure(
|
||||
throw new SongOrChartNotFoundFailure(
|
||||
`Chart ${context.chartHash} is not on the IR.`,
|
||||
"ir/usc",
|
||||
data,
|
||||
|
||||
@@ -60,7 +60,7 @@ router.post(
|
||||
|
||||
const importType = req.safeBody.importType as FileUploadImportTypes;
|
||||
|
||||
const userIntent = !!req.header("X-User-Intent");
|
||||
const userIntent = req.header("X-User-Intent")?.toLowerCase() === "true";
|
||||
|
||||
if (ServerConfig.USE_EXTERNAL_SCORE_IMPORT_WORKER) {
|
||||
const importID = Random20Hex();
|
||||
@@ -120,7 +120,7 @@ router.post(
|
||||
|
||||
const userID = req[SYMBOL_TACHI_API_AUTH].userID!;
|
||||
|
||||
const userIntent = !!req.header("X-User-Intent");
|
||||
const userIntent = req.header("X-User-Intent")?.toLowerCase() === "true";
|
||||
|
||||
if (ServerConfig.USE_EXTERNAL_SCORE_IMPORT_WORKER) {
|
||||
const job: ScoreImportJobData<APIImportTypes> = {
|
||||
|
||||
@@ -42,10 +42,10 @@ router.post("/submit-score", RequireNotGuest, async (req, res) => {
|
||||
const type = importRes.body.body.errors[0].type;
|
||||
const errMsg = importRes.body.body.errors[0].message;
|
||||
|
||||
// If the error type is KTDataNotFound, then we **know** that
|
||||
// If the error type is SongOrChartNotFound, then we **know** that
|
||||
// the chart and score values were atleast typed correctly
|
||||
// and can afford to make this assertion.
|
||||
if (type === "KTDataNotFound") {
|
||||
if (type === "SongOrChartNotFound") {
|
||||
const { chart } = req.safeBody as { chart: BeatorajaChart };
|
||||
|
||||
const orphanInfo: { userIDs: Array<integer> } | null = await db[
|
||||
@@ -58,7 +58,7 @@ router.post("/submit-score", RequireNotGuest, async (req, res) => {
|
||||
);
|
||||
|
||||
if (!orphanInfo) {
|
||||
logger.warn(`Chart '${chart.sha256}' got KTDataNotFound, but was not orphaned?`, {
|
||||
logger.warn(`Chart '${chart.sha256}' got SongOrChartNotFound, but was not orphaned?`, {
|
||||
body: req.safeBody as unknown,
|
||||
});
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ router.post(
|
||||
RequirePermissions("submit_score"),
|
||||
ScoreImportRateLimiter,
|
||||
async (req, res) => {
|
||||
const userIntent = !!req.header("X-User-Intent");
|
||||
const userIntent = req.header("X-User-Intent")?.toLowerCase() === "true";
|
||||
|
||||
if (ServerConfig.USE_EXTERNAL_SCORE_IMPORT_WORKER) {
|
||||
const importID = Random20Hex();
|
||||
|
||||
@@ -313,7 +313,7 @@ router.post("/scores", RequirePermissions("submit_score"), async (req, res) => {
|
||||
|
||||
// If the import failed, AND the import failure WAS NOT that the chart didnt exist
|
||||
// report that error instead.
|
||||
if (importDoc.errors[0] && importDoc.errors[0].type !== "KTDataNotFound") {
|
||||
if (importDoc.errors[0] && importDoc.errors[0].type !== "SongOrChartNotFound") {
|
||||
logger.info(`USC Import Failed ${importDoc.errors[0].message}`, {
|
||||
importDoc,
|
||||
userID,
|
||||
|
||||
@@ -58,7 +58,7 @@ export function FindChartWithPTDFVersion<
|
||||
|
||||
export function FindITGChartOnHash(hash: string) {
|
||||
return db.charts.itg.findOne({
|
||||
"data.chartHash": hash,
|
||||
"data.hashGSV3": hash,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user