From af937b0bf667a2088f3fdc39263448ddfa587bbd Mon Sep 17 00:00:00 2001 From: zkldi Date: Tue, 1 Jun 2021 21:23:29 +0100 Subject: [PATCH] api/arc-jubeat support --- .../import-types/api/arc-jubeat/converter.ts | 98 +++++++++++++++++++ .../import-types/api/arc-jubeat/parser.ts | 26 +++++ .../import-types/api/arc-jubeat/types.ts | 8 ++ 3 files changed, 132 insertions(+) create mode 100644 server/src/lib/score-import/import-types/api/arc-jubeat/converter.ts create mode 100644 server/src/lib/score-import/import-types/api/arc-jubeat/parser.ts create mode 100644 server/src/lib/score-import/import-types/api/arc-jubeat/types.ts diff --git a/server/src/lib/score-import/import-types/api/arc-jubeat/converter.ts b/server/src/lib/score-import/import-types/api/arc-jubeat/converter.ts new file mode 100644 index 000000000..7882dcc0d --- /dev/null +++ b/server/src/lib/score-import/import-types/api/arc-jubeat/converter.ts @@ -0,0 +1,98 @@ +import { EmptyObject } from "../../../../../utils/types"; +import { ConverterFunction } from "../../common/types"; +import p, { PrudenceSchema } from "prudence"; +import { + InvalidScoreFailure, + KTDataNotFoundFailure, +} from "../../../framework/common/converter-failures"; +import { FormatPrError } from "../../../../../utils/prudence"; +import { ARCJubeatScore } from "./types"; +import { FindChartOnARCID } from "../../../../../utils/queries/charts"; +import { FindSongOnIDGuaranteed } from "../../../../../utils/queries/songs"; +import { DryScore } from "../../../framework/common/types"; +import { + GenericGetGradeAndPercent, + ParseDateFromString, +} from "../../../framework/common/score-utils"; +import { Lamps, integer } from "kamaitachi-common"; + +// There's a bunch of other useless fields but we don't care +const PR_ArcJubeatScore: PrudenceSchema = { + chart_id: "string", + clear_type: p.isIn("FULL_COMBO", "CLEAR", "EXC", "FAIL"), + score: p.isBoundedInteger(0, 1_000_000), + timestamp: "string", +}; + +export const ConvertAPIArcJubeat: ConverterFunction = async ( + data, + context, + importType, + logger +) => { + const err = p(data, PR_ArcJubeatScore, {}, { throwOnNonObject: false, allowExcessKeys: true }); + + if (err) { + throw new InvalidScoreFailure(FormatPrError(err, "Invalid ARC Score: ")); + } + + // confirmed by Prudence above. + const score = data as ARCJubeatScore; + + const chart = await FindChartOnARCID("jubeat", score.chart_id); + + if (!chart) { + throw new KTDataNotFoundFailure( + `Could not find chart with chart_id ${score.chart_id}.`, + importType, + data, + context + ); + } + + const song = await FindSongOnIDGuaranteed("jubeat", chart.songID, logger); + + const { grade, percent } = GenericGetGradeAndPercent("jubeat", score.score, chart); + + const timeAchieved = ParseDateFromString(score.timestamp); + + const lamp = ResolveARCJubeatLamp(score.score, score.clear_type); + + const dryScore: DryScore<"jubeat:Single"> = { + comment: null, + game: "jubeat", + importType, + timeAchieved, + service: "ARC Jubeat clan", + scoreData: { + grade, + percent, + score: score.score, + hitData: {}, + hitMeta: {}, + lamp, + }, + scoreMeta: {}, + }; + + return { song, chart, dryScore }; +}; + +/** + * ARC has a bug where all jubeat scores are clears no matter what. + * This function takes the score and lamp and rederives the lamp. + */ +function ResolveARCJubeatLamp( + score: integer, + lamp: ARCJubeatScore["clear_type"] +): Lamps["jubeat:Single"] { + if (score === 1_000_000) { + return "EXCELLENT"; + } else if (lamp === "FULL_COMBO") { + return "FULL COMBO"; + } else if (score >= 700_000) { + return "CLEAR"; + } + + return "FAILED"; +} diff --git a/server/src/lib/score-import/import-types/api/arc-jubeat/parser.ts b/server/src/lib/score-import/import-types/api/arc-jubeat/parser.ts new file mode 100644 index 000000000..25d52e031 --- /dev/null +++ b/server/src/lib/score-import/import-types/api/arc-jubeat/parser.ts @@ -0,0 +1,26 @@ +import { KtLogger } from "../../../../logger/logger"; +import nodeFetch from "../../../../../utils/fetch"; +import { TraverseKaiAPI } from "../../common/api-kai/traverse-api"; +import { ParserFunctionReturnsAsync } from "../../common/types"; +import { EmptyObject } from "../../../../../utils/types"; +import { ARC_API_URL, ARC_AUTH_TOKEN } from "../../../../env/env"; + +export function ParseArcJubeat( + arcProfileID: string, + logger: KtLogger, + fetch = nodeFetch +): ParserFunctionReturnsAsync { + return { + iterable: TraverseKaiAPI( + ARC_API_URL, + // clan + `/api/v1/jb/8/player_bests?profile_id=${arcProfileID}`, + ARC_AUTH_TOKEN, + logger, + fetch + ), + context: {}, + classHandler: null, + game: "jubeat", + }; +} diff --git a/server/src/lib/score-import/import-types/api/arc-jubeat/types.ts b/server/src/lib/score-import/import-types/api/arc-jubeat/types.ts new file mode 100644 index 000000000..fde93f404 --- /dev/null +++ b/server/src/lib/score-import/import-types/api/arc-jubeat/types.ts @@ -0,0 +1,8 @@ +import { integer } from "kamaitachi-common"; + +export interface ARCJubeatScore { + chart_id: string; + clear_type: "FULL_COMBO" | "CLEAR" | "FAIL" | "EXC"; + score: integer; + timestamp: string; +}