Merge pull request #819 from TNG-dev/zkldi/explicit-matchType

This commit is contained in:
zkldi
2022-12-26 16:00:26 +00:00
committed by GitHub
44 changed files with 316 additions and 971398 deletions
+43
View File
@@ -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
View File
@@ -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
-5
View File
@@ -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);
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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.
+2 -2
View File
@@ -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,
+1 -1
View File
@@ -58,7 +58,7 @@ export function FindChartWithPTDFVersion<
export function FindITGChartOnHash(hash: string) {
return db.charts.itg.findOne({
"data.chartHash": hash,
"data.hashGSV3": hash,
});
}