Merge pull request #670 from hewoicvewse/sgl-tables

This commit is contained in:
zkldi
2022-09-05 20:01:49 +01:00
committed by GitHub
4 changed files with 251 additions and 0 deletions
+10
View File
@@ -253,3 +253,13 @@ export function FormatSieglindePMS(sgl: number): string {
return `●${(sgl - 12).toFixed(2)}`;
}
export function CreateSongMap<G extends Game = Game>(songs: Array<SongDocument<G>>) {
const songMap = new Map<integer, SongDocument<G>>();
for (const song of songs) {
songMap.set(song.id, song);
}
return songMap;
}
@@ -0,0 +1,54 @@
import t from "tap";
import mockApi from "test-utils/mock-api";
t.test("GET /api/v1/games/bms/7K/content", (t) => {
t.test("Should exist.", async (t) => {
const res = await mockApi.get("/api/v1/games/bms/7K/content");
t.equal(res.body.success, true);
t.end();
});
t.end();
});
t.test("GET /api/v1/games/bms/7K/content/sieglindeEC", (t) => {
t.test("Should point to the right file.", async (t) => {
const res = await mockApi.get("/api/v1/games/bms/7K/content/sieglindeEC/header.json");
t.equal(res.body.data_url, "./body.json");
t.end();
});
t.test("`body.json` should return an array.", async (t) => {
const res = await mockApi.get("/api/v1/games/bms/7K/content/sieglindeEC/body.json");
t.equal(Array.isArray(res.body), true);
t.end();
});
t.end();
});
t.test("GET /api/v1/games/bms/7K/content/sieglindeHC", (t) => {
t.test("Should point to the right file.", async (t) => {
const res = await mockApi.get("/api/v1/games/bms/7K/content/sieglindeHC/header.json");
t.equal(res.body.data_url, "./body.json");
t.end();
});
t.test("`body.json` should return an array.", async (t) => {
const res = await mockApi.get("/api/v1/games/bms/7K/content/sieglindeHC/body.json");
t.equal(Array.isArray(res.body), true);
t.end();
});
t.end();
});
@@ -0,0 +1,178 @@
import { Router } from "express";
import db from "external/mongo/db";
import CreateLogCtx from "lib/logger/logger";
import { CreateSongMap } from "tachi-common";
import { integer, SongDocument } from "tachi-common/types";
import { GetFolderCharts, GetFoldersFromTable } from "utils/folder";
import { GetTachiData } from "utils/req-tachi-data";
import type { Request, Response } from "express";
import type { ChartDocument } from "tachi-common/types";
const logger = CreateLogCtx(__filename);
const router: Router = Router({ mergeParams: true });
/**
* Returns "Endpoint exists"
*
* @name GET /api/v1/games/bms/7K/content
*/
router.get("/", (req, res) => {
return res.status(200).json({
success: true,
description: "Endpoint exists",
});
});
/**
* Utility function for mounting a BMS-style Table. This defines
* a header.json endpoint, and a body.json endpoint.
*
* It also defines a HTML-emitting endpoint that contains a pointer
* to said header.json file.
*/
function CreateAndMountTable(
router: Router,
tableName: string,
headerHandler: (req: Request, res: Response) => any,
bodyHandler: (req: Request, res: Response) => any
) {
router.get(`/${tableName}`, (req, res) => {
return res.status(200).send(`<html>
<head>
<meta name="bmstable" value="./${tableName}/header.json">
</head>
<body>This is a stub page for beatoraja compatibility. No useful information here!</body>
</html>`);
});
router.get(`/${tableName}/header.json`, headerHandler);
router.get(`/${tableName}/body.json`, bodyHandler);
}
/**
* Returns a difficulty table of all charts rated according to their Sieglinde values for easy clears
*
* @name GET /api/v1/games/bms/7k/content/sieglindeEC
*/
CreateAndMountTable(
router,
"sieglindeEC",
/* header.json */ (_req, res) => {
return res.status(200).send({
name: "Sieglinde EC",
symbol: "sgl-",
data_url: "./body.json",
});
},
/* body.json */ async (req, res) => {
const game = "bms";
const table = await db.tables.findOne({
tableID: "bms-7K-sgl-EC",
game,
playtype: "7K",
});
if (!table) {
logger.error(
"Could not find table bms-7K-sgl-EC. Cannot convert sieglinde tables into BMS form!"
);
return res.status(500).json([]);
}
const folders = await GetFoldersFromTable(table);
const response = [];
for (const folder of folders) {
const data = await GetFolderCharts(folder, {}, true);
const charts = data.charts as Array<ChartDocument<"bms:7K">>;
const songMap = CreateSongMap(data.songs);
for (const chart of charts) {
const song = songMap.get(chart.songID);
if (!song) {
response.push({
level: folder.title,
md5: chart.data.hashMD5,
});
} else {
response.push({
level: folder.title,
title: song.title,
artist: song.artist,
md5: chart.data.hashMD5,
});
}
}
}
return res.status(200).json(response);
}
);
/**
* Returns a difficulty table of all charts rated according to their Sieglinde values for hard clears
*
* @name GET /api/v1/games/bms/7k/content/sieglindeHC
*/
CreateAndMountTable(
router,
"sieglindeHC",
/* header.json */ (_req, res) => {
return res.status(200).send({
name: "Sieglinde HC",
symbol: "sgl-",
data_url: "./body.json",
});
},
/* body.json */ async (req, res) => {
const game = "bms";
const table = await db.tables.findOne({
tableID: "bms-7K-sgl-HC",
game,
playtype: "7K",
});
if (!table) {
logger.error(
"Could not find table bms-7K-sgl-HC. Cannot convert sieglinde tables into BMS form!"
);
return res.status(500).json([]);
}
const folders = await GetFoldersFromTable(table);
const response = [];
for (const folder of folders) {
const data = await GetFolderCharts(folder, {}, true);
const charts = data.charts as Array<ChartDocument<"bms:7K">>;
const songMap = CreateSongMap(data.songs);
for (const chart of charts) {
const song = songMap.get(chart.songID);
if (!song) {
response.push({
level: folder.title,
md5: chart.data.hashMD5,
});
} else {
response.push({
level: folder.title,
title: song.title,
artist: song.artist,
md5: chart.data.hashMD5,
});
}
}
}
return res.status(200).json(response);
}
);
export default router;
@@ -1,3 +1,4 @@
import bmsContentRouter from "./_game/_playtype/content/bms-router";
import gameRouter from "./_game/router";
import { Router } from "express";
import { TachiConfig } from "lib/setup/config";
@@ -25,6 +26,14 @@ router.get("/", (req, res) => {
});
});
// "Content" endpoints are game-playtype specific.
// as such, we want to mount them *before* any generic endpoints
// so that they'll guaranteeably get hit.
// This is a bit unorthodox, but arguably necessary, as all /content
// endpoints really are *entirely* GPT specific.
router.use("/bms/7K/content", bmsContentRouter);
router.use("/:game", gameRouter);
export default router;