feat: remove MER from codebase

Hasn't been used in forever.
This commit is contained in:
zkldi
2023-11-14 22:23:51 +00:00
parent ddf9080fb4
commit b9e7e04b6e
17 changed files with 0 additions and 550 deletions
@@ -533,16 +533,6 @@ function ImportTypeInfoCard({
key="Batch Manual"
/>
);
case "file/mer-iidx":
return (
<ImportInfoCard
name="MER .json"
href="iidx-mer"
desc="Use a MER-exported .json file to synchronise scores."
moreInfo="Note: Scores that do not match an existing song on the site will be orphaned, and anyone can see your orphaned scores. Don't be an idiot. I highly recommend using Fervidex instead."
key="MER .json"
/>
);
case "file/pli-iidx-csv":
return (
<ImportInfoCard
@@ -1,31 +0,0 @@
import ImportFileInfo from "components/imports/ImportFileInfo";
import useSetSubheader from "components/layout/header/useSetSubheader";
import React from "react";
export default function MerJSONPage() {
useSetSubheader(["Import Scores", "MER .json"]);
return (
<ImportFileInfo
acceptMime="application/json"
importType="file/mer-iidx"
name="MER .json"
parseFunction={ParseFunction}
/>
);
}
function ParseFunction(d: string) {
const json = JSON.parse(d);
if (!Array.isArray(json)) {
throw new Error(`Expected a JSON array. Is this valid MER?`);
}
return {
valid: true,
info: {
Scores: json.length,
},
};
}
-1
View File
@@ -11,7 +11,6 @@ const FILE_IMPORT_TYPES: Record<FileUploadImportTypes, true> = {
"file/eamusement-sdvx-csv": true,
"file/batch-manual": true,
"file/solid-state-squad": true,
"file/mer-iidx": true,
"file/pli-iidx-csv": true,
"file/mypagescraper-records-csv": true,
"file/mypagescraper-player-csv": true,
-1
View File
@@ -2,7 +2,6 @@ export type FileUploadImportTypes =
| "file/batch-manual"
| "file/eamusement-iidx-csv"
| "file/eamusement-sdvx-csv"
| "file/mer-iidx"
| "file/mypagescraper-player-csv"
| "file/mypagescraper-records-csv"
| "file/pli-iidx-csv"
@@ -51,7 +51,6 @@ is set to indicate that this was done with User Intent.
| `file/eamusement-iidx-csv` | The E-amusement CSV format for IIDX. This type accepts both Pre-HV and Post-HV formats. | Kamaitachi | Yes |
| `file/batch-manual` | The Tachi BATCH-MANUAL format submitted through a multipart form. | Kamaitachi & Bokutachi | Yes |
| `file/solid-state-squad` | The IIDX XML output by Solid State Squad. | Kamaitachi | Yes |
| `file/mer-iidx` | The IIDX JSON output from MER. | Kamaitachi | Yes |
| `file/pli-iidx-csv` | Same as eamusement's IIDX CSV, but output from PLI. | Kamaitachi | Yes |
| `ir/direct-manual` | The Tachi BATCH-MANUAL format but submitted in a HTTP Request body as `application/json`. | Kamaitachi & Bokutachi | Depends on Header |
| `ir/barbatos` | Barbatos's format submitted in a HTTP Request Body. | Kamaitachi | No |
-1
View File
@@ -68,7 +68,6 @@ the main benefits for us are as follows:
"file/eamusement-iidx-csv",
"file/batch-manual",
"file/solid-state-squad",
"file/mer-iidx",
"file/pli-iidx-csv",
"ir/direct-manual",
"ir/barbatos",
-1
View File
@@ -53,7 +53,6 @@
"file/eamusement-iidx-csv",
"file/batch-manual",
"file/solid-state-squad",
"file/mer-iidx",
"file/pli-iidx-csv",
"file/mypagescraper-records-csv",
"file/mypagescraper-player-csv",
@@ -1,7 +1,6 @@
import type { ConverterFailure } from "../../framework/common/converter-failures";
import type { DryScore } from "../../framework/common/types";
import type { SDVXEamusementCSVData } from "../file/eamusement-sdvx-csv/types";
import type { MerScore } from "../file/mer-iidx/types";
import type { S3Score } from "../file/solid-state-squad/types";
import type { MyPageRecordsParsedPB } from "../file/wacca-mypage-scraper/types";
import type { BarbatosContext, BarbatosScore, BarbatosSDVX6Score } from "../ir/barbatos/types";
@@ -34,7 +33,6 @@ export interface ImportTypeDataMap {
"file/eamusement-sdvx-csv": SDVXEamusementCSVData;
"file/batch-manual": BatchManualScore;
"file/solid-state-squad": S3Score;
"file/mer-iidx": MerScore;
"file/pli-iidx-csv": IIDXEamusementCSVData;
"file/mypagescraper-records-csv": MyPageRecordsParsedPB;
"file/mypagescraper-player-csv": never;
@@ -79,7 +77,6 @@ export interface ImportTypeContextMap {
"file/eamusement-sdvx-csv": EmptyObject;
"file/batch-manual": BatchManualContext;
"file/solid-state-squad": EmptyObject;
"file/mer-iidx": EmptyObject;
"file/pli-iidx-csv": IIDXEamusementCSVContext;
"file/mypagescraper-records-csv": EmptyObject;
"file/mypagescraper-player-csv": EmptyObject;
@@ -6,7 +6,6 @@ import { ConvertAPIKaiSDVX } from "./common/api-kai/sdvx/converter";
import { ConverterBatchManual } from "./common/batch-manual/converter";
import ConvertEamIIDXCSV from "./common/eamusement-iidx-csv/converter";
import ConvertEamSDVXCSV from "./file/eamusement-sdvx-csv/converter";
import { ConvertFileMerIIDX } from "./file/mer-iidx/converter";
import { ConvertFileS3 } from "./file/solid-state-squad/converter";
import ConvertMyPageScraperRecordsCSV from "./file/wacca-mypage-scraper/converter";
import { ConverterIRBarbatos } from "./ir/barbatos/converter";
@@ -28,7 +27,6 @@ export type ConverterMap = {
export const Converters: ConverterMap = {
"file/solid-state-squad": ConvertFileS3,
"file/batch-manual": ConverterBatchManual,
"file/mer-iidx": ConvertFileMerIIDX,
"file/pli-iidx-csv": ConvertEamIIDXCSV,
"file/eamusement-iidx-csv": ConvertEamIIDXCSV,
"file/eamusement-sdvx-csv": ConvertEamSDVXCSV,
@@ -1,127 +0,0 @@
import { ConvertFileMerIIDX } from "./converter";
import deepmerge from "deepmerge";
import db from "external/mongo/db";
import CreateLogCtx from "lib/logger/logger";
import t from "tap";
import ResetDBState from "test-utils/resets";
import { LoadTachiIIDXData, Testing511Song, Testing511SPA } from "test-utils/test-data";
import type { MerScore } from "./types";
const logger = CreateLogCtx(__filename);
t.test("#ConvertFileMerIIDX", (t) => {
t.beforeEach(ResetDBState);
const MerScore = {
music_id: 1000,
play_type: "SINGLE",
diff_type: "ANOTHER",
score: 1000,
miss_count: 21,
clear_type: "CLEAR",
update_time: "2021-03-24 07:15:22",
} as const;
function merc(g: Partial<MerScore> = {}) {
return ConvertFileMerIIDX(deepmerge(MerScore, g), {}, "file/mer-iidx", logger);
}
t.test("Valid Conversion", async (t) => {
const res = await ConvertFileMerIIDX(MerScore, {}, "file/mer-iidx", logger);
t.hasStrict(
res,
{
song: Testing511Song,
chart: Testing511SPA,
dryScore: {
game: "iidx",
comment: null,
importType: "file/mer-iidx",
service: "MER",
scoreData: {
score: 1000,
lamp: "CLEAR",
judgements: {},
optional: {
bp: 21,
},
},
scoreMeta: {},
timeAchieved: 1616537722000,
},
},
"Should return the converted dry score."
);
t.end();
});
t.test("Valid DP Conversion", async (t) => {
await LoadTachiIIDXData();
const res = await merc({ play_type: "DOUBLE" });
t.hasStrict(
res,
{
song: { id: 1 },
chart: { playtype: "DP", data: { inGameID: 1000 } },
dryScore: {
game: "iidx",
comment: null,
importType: "file/mer-iidx",
service: "MER",
scoreData: {
score: 1000,
lamp: "CLEAR",
judgements: {},
optional: {
bp: 21,
},
},
scoreMeta: {},
timeAchieved: 1616537722000,
},
},
"Should return the converted dry score."
);
t.end();
});
t.test("Invalid Chart", (t) => {
t.rejects(() => merc({ diff_type: "LEGGENDARIA" }), {
message: /Could not find chart with musicID 1000 \(SP LEGGENDARIA/u,
});
t.end();
});
t.test("Invalid Song", (t) => {
t.rejects(() => merc({ music_id: 0 }), {
message: /Could not find chart with musicID 0/u,
});
t.end();
});
t.test("Should log severe on Song-Chart Desync", async (t) => {
await db.songs.iidx.remove({});
t.rejects(() => merc(), {
message: /Song-Chart Desync on songID 1/u,
});
t.end();
});
t.test("Invalid Date", (t) => {
t.rejects(() => merc({ update_time: "INVALID" }), {
message: /Invalid\/Unparsable score timestamp of INVALID/u,
});
t.end();
});
t.end();
});
@@ -1,91 +0,0 @@
import {
InternalFailure,
SkipScoreFailure,
SongOrChartNotFoundFailure,
} from "../../../framework/common/converter-failures";
import { ParseDateFromString } from "../../../framework/common/score-utils";
import { FindIIDXChartOnInGameID } from "utils/queries/charts";
import { FindSongOnID } from "utils/queries/songs";
import type { DryScore } from "../../../framework/common/types";
import type { ConverterFunction } from "../../common/types";
import type { MerScore } from "./types";
import type { GetEnumValue } from "tachi-common/types/metrics";
import type { EmptyObject } from "utils/types";
function ConvertMERLamp(lamp: MerScore["clear_type"]): GetEnumValue<"iidx:DP" | "iidx:SP", "lamp"> {
if (lamp === "FULLCOMBO CLEAR") {
return "FULL COMBO";
}
return lamp;
}
export const ConvertFileMerIIDX: ConverterFunction<MerScore, EmptyObject> = async (
data,
context,
importType,
logger
) => {
const playtype = data.play_type === "SINGLE" ? "SP" : "DP";
if (data.diff_type === "BEGINNER") {
throw new SkipScoreFailure(`BEGINNER scores are not supported.`);
}
const chart = await FindIIDXChartOnInGameID(data.music_id, playtype, data.diff_type);
if (!chart) {
throw new SongOrChartNotFoundFailure(
`Could not find chart with musicID ${data.music_id} (${playtype} ${data.diff_type}.)`,
importType,
data,
context
);
}
const song = await FindSongOnID("iidx", chart.songID);
if (!song) {
logger.severe(`Could not find song with songID ${chart.songID}, but chart exists for it?`);
throw new InternalFailure(`Song-Chart Desync on songID ${chart.songID}`);
}
const lamp = ConvertMERLamp(data.clear_type);
const timeAchieved = ParseDateFromString(ConvertDateToJST(data.update_time));
const dryScore: DryScore<"iidx:DP" | "iidx:SP"> = {
game: "iidx",
comment: null,
importType: "file/mer-iidx",
service: "MER",
scoreData: {
score: data.score,
lamp,
judgements: {},
optional: {
bp: data.miss_count === -1 ? null : data.miss_count,
},
},
scoreMeta: {},
timeAchieved: timeAchieved !== null ? timeAchieved : null,
};
return {
chart,
song,
dryScore,
};
};
/**
* MER passes us a rather useless date without any timezone information.
* To ensure that this parses properly regardless of timezone, we need to
* assert thathere.
*
* @param uselessDate - A date string like 2021-03-24 07:15:22.
* As you can see, it has no timezone information, and is ambiguous.
*/
function ConvertDateToJST(uselessDate: string) {
return `${uselessDate.replace(" ", "T")}+09:00`;
}
@@ -1,160 +0,0 @@
import { ParseMerIIDX } from "./parser";
import deepmerge from "deepmerge";
import CreateLogCtx from "lib/logger/logger";
import t from "tap";
import { MockMulterFile } from "test-utils/mock-multer";
import ResetDBState from "test-utils/resets";
import { GetKTDataBuffer, GetKTDataJSON } from "test-utils/test-data";
const logger = CreateLogCtx(__filename);
t.test("#ParseMerIIDX", (t) => {
t.beforeEach(ResetDBState);
function mrfb(buffer: Buffer) {
return ParseMerIIDX(MockMulterFile(buffer, "buffer.json"), {}, logger);
}
function mrfj(obj: unknown) {
return ParseMerIIDX(
MockMulterFile(Buffer.from(JSON.stringify([obj])), "buffer.json"),
{},
logger
);
}
function mrff(filename: string) {
return ParseMerIIDX(MockMulterFile(GetKTDataBuffer(filename), filename), {}, logger);
}
t.test("Basic Parsing", (t) => {
const res = mrff("./mer/base.json");
t.hasStrict(
res,
{
game: "iidx",
context: {},
classProvider: null,
iterable: [
{
music_id: 3007,
music_name: "Presto",
play_type: "SINGLE",
diff_type: "HYPER",
score: 566,
miss_count: 46,
clear_type: "NO PLAY",
update_time: "2019-06-01 19:56:59",
},
{
music_id: 3013,
music_name: "THE SAFARI",
play_type: "SINGLE",
diff_type: "NORMAL",
score: 681,
miss_count: 102,
clear_type: "FAILED",
update_time: "2019-05-30 03:21:57",
},
{
music_id: 3213,
music_name: "TAKE ON ME",
play_type: "SINGLE",
diff_type: "ANOTHER",
score: 922,
miss_count: 56,
clear_type: "CLEAR",
update_time: "2019-05-03 02:10:58",
},
],
},
"Should correctly parse data."
);
t.end();
});
const baseScore = {
music_id: 3007,
version_id: 3,
version_name: "3rd style",
music_name: "Presto",
play_type: "SINGLE",
diff_type: "HYPER",
score: 566,
score_percent: 56.8273,
miss_count: 46,
grade: "B",
clear_type: "NO PLAY",
clear_type_id: 0,
level: 8,
note: 498,
play_count: 6,
update_time: "2019-06-01 19:56:59",
score_diff_str: "B+12",
};
t.test("Should throw on invalid play_type", (t) => {
t.throws(() => mrfj(deepmerge(baseScore, { play_type: "INVALID" })));
t.throws(() => mrfj(deepmerge(baseScore, { play_type: null })));
t.throws(() => mrfj(deepmerge(baseScore, { play_type: undefined })));
t.throws(() => mrfj(deepmerge(baseScore, { play_type: 1 })));
t.end();
});
t.test("Should throw on invalid score", (t) => {
t.throws(() => mrfj(deepmerge(baseScore, { score: -1 })));
t.throws(() => mrfj(deepmerge(baseScore, { score: 1.5 })));
t.throws(() => mrfj(deepmerge(baseScore, { score: "1" })));
t.throws(() => mrfj(deepmerge(baseScore, { score: null })));
t.throws(() => mrfj(deepmerge(baseScore, { score: undefined })));
t.end();
});
t.test("Should throw on invalid diff_type", (t) => {
t.throws(() => mrfj(deepmerge(baseScore, { diff_type: "INVALID" })));
t.throws(() => mrfj(deepmerge(baseScore, { diff_type: null })));
t.throws(() => mrfj(deepmerge(baseScore, { diff_type: undefined })));
t.end();
});
t.test("Should throw on invalid clear_type", (t) => {
t.throws(() => mrfj(deepmerge(baseScore, { clear_type: "INVALID" })));
t.throws(() => mrfj(deepmerge(baseScore, { clear_type: null })));
t.throws(() => mrfj(deepmerge(baseScore, { clear_type: undefined })));
t.end();
});
t.test("Should throw on invalid miss_count", (t) => {
t.throws(() => mrfj(deepmerge(baseScore, { miss_count: "INVALID" })));
t.throws(() => mrfj(deepmerge(baseScore, { miss_count: null })));
t.throws(() => mrfj(deepmerge(baseScore, { miss_count: undefined })));
t.end();
});
t.test("Should throw on invalid JSON", (t) => {
t.throws(() => mrfb(Buffer.from("{]")));
t.end();
});
t.test("Should throw on non-top-level-array", (t) => {
t.throws(() => mrfb(Buffer.from("{}")));
t.end();
});
t.test("Should throw on invalid Mer Scores", (t) => {
t.throws(() => mrfb(Buffer.from(JSON.stringify([{ foo: "bar" }]))));
t.end();
});
t.end();
});
@@ -1,61 +0,0 @@
import ScoreImportFatalError from "../../../framework/score-importing/score-import-error";
import { p } from "prudence";
import { FormatPrError } from "utils/prudence";
import type { ParserFunctionReturns } from "../../common/types";
import type { MerScore } from "./types";
import type { KtLogger } from "lib/logger/logger";
import type { EmptyObject } from "utils/types";
const PR_MER_IIDX = {
music_id: p.isPositiveInteger,
play_type: p.isIn("SINGLE", "DOUBLE"),
diff_type: p.isIn("NORMAL", "HYPER", "ANOTHER", "LEGGENDARIA", "BEGINNER"),
score: p.isPositiveInteger,
miss_count: p.or(p.isPositiveInteger, p.is(-1)),
clear_type: p.isIn(
"NO PLAY",
"FAILED",
"ASSIST CLEAR",
"EASY CLEAR",
"CLEAR",
"HARD CLEAR",
"EX HARD CLEAR",
"FULLCOMBO CLEAR"
),
update_time: "string",
};
export function ParseMerIIDX(
fileData: Express.Multer.File,
body: Record<string, unknown>,
logger: KtLogger
): ParserFunctionReturns<MerScore, EmptyObject> {
let jsonData;
try {
jsonData = JSON.parse(fileData.buffer.toString("utf-8")) as unknown;
} catch (err) {
logger.info((err as Error).message);
throw new ScoreImportFatalError(400, "Invalid JSON.");
}
if (!Array.isArray(jsonData)) {
throw new ScoreImportFatalError(400, `Invalid MER-IIDX JSON: Expected Top-Level Array.`);
}
// this is because prudence doesn't support top level arrays at the moment.
for (let i = 0; i < jsonData.length; i++) {
const err = p(jsonData[i], PR_MER_IIDX, {}, { allowExcessKeys: true });
if (err) {
throw new ScoreImportFatalError(400, `${FormatPrError(err)} [Index ${i}]`);
}
}
return {
classProvider: null,
context: {},
iterable: jsonData as Array<MerScore>,
game: "iidx",
};
}
@@ -1,19 +0,0 @@
import type { integer } from "tachi-common";
export interface MerScore {
music_id: integer;
play_type: "DOUBLE" | "SINGLE";
diff_type: "ANOTHER" | "BEGINNER" | "HYPER" | "LEGGENDARIA" | "NORMAL";
score: integer;
miss_count: integer;
clear_type:
| "ASSIST CLEAR"
| "CLEAR"
| "EASY CLEAR"
| "EX HARD CLEAR"
| "FAILED"
| "FULLCOMBO CLEAR"
| "HARD CLEAR"
| "NO PLAY";
update_time: string;
}
@@ -17,7 +17,6 @@ import {
import ParseBatchManual from "./file/batch-manual/parser";
import ParseEamusementIIDXCSV from "./file/eamusement-iidx-csv/parser";
import ParseEamusementSDVXCSV from "./file/eamusement-sdvx-csv/parser";
import { ParseMerIIDX } from "./file/mer-iidx/parser";
import ParsePLIIIDXCSV from "./file/pli-iidx-csv/parser";
import { ParseSolidStateXML } from "./file/solid-state-squad/parser";
import {
@@ -37,7 +36,6 @@ import { ParseIRUSC } from "./ir/usc/parser";
export const Parsers = {
"file/solid-state-squad": ParseSolidStateXML,
"file/batch-manual": ParseBatchManual,
"file/mer-iidx": ParseMerIIDX,
"file/pli-iidx-csv": ParsePLIIIDXCSV,
"file/eamusement-iidx-csv": ParseEamusementIIDXCSV,
"file/eamusement-sdvx-csv": ParseEamusementSDVXCSV,
@@ -301,44 +301,6 @@ t.test("POST /api/v1/import/file", async (t) => {
t.end();
});
t.test("file/mer-iidx", (t) => {
t.beforeEach(LoadTachiIIDXData);
t.test("Example Import", async (t) => {
const res = await mockApi
.post("/api/v1/import/file")
.set("Cookie", cookie)
.attach("scoreData", GetKTDataBuffer("./mer/base.json"), "base.json")
.field("importType", "file/mer-iidx");
t.equal(res.body.success, true, "Should be successful");
t.equal(res.body.body.errors.length, 0, "Import Should have 0 failed scores.");
t.equal(res.body.body.scoreIDs.length, 3, "Should have 3 successful scores.");
t.end();
});
t.test("Example Import", async (t) => {
const res = await mockApi
.post("/api/v1/import/file")
.set("Cookie", cookie)
.attach("scoreData", GetKTDataBuffer("./mer/large.json"), "base.json")
.field("importType", "file/mer-iidx");
t.equal(res.body.success, true, "Should be successful");
t.equal(res.body.body.errors.length, 0, "Import Should have 0 failed scores.");
t.equal(res.body.body.scoreIDs.length, 627, "Should have 627 successful scores.");
t.end();
});
t.end();
});
t.skip("file/solid-state-squad", (t) => {
t.beforeEach(LoadTachiIIDXData);
-1
View File
@@ -55,7 +55,6 @@
"file/eamusement-iidx-csv",
"file/batch-manual",
"file/solid-state-squad",
"file/mer-iidx",
"file/pli-iidx-csv",
"ir/direct-manual",
"ir/barbatos",