mirror of
https://github.com/zkldi/Tachi.git
synced 2026-09-27 01:20:31 +03:00
feat: rival-info table
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { ServerConfig } from "lib/setup/config";
|
||||
import { GetRivalUsers } from "lib/rivals/rivals";
|
||||
import { ServerConfig, TachiConfig } from "lib/setup/config";
|
||||
import { CreateSongMap } from "tachi-common";
|
||||
import { GetRelevantSongsAndCharts } from "utils/db";
|
||||
import {
|
||||
GetFolderCharts,
|
||||
GetFolderNamesInOrder,
|
||||
GetFoldersFromTable,
|
||||
GetTableForIDGuaranteed,
|
||||
} from "utils/folder";
|
||||
import { GetRecentUGPTScores } from "utils/queries/scores";
|
||||
import path from "path";
|
||||
import type { BMSTableEntry, BMSTableHead } from "bms-table-loader";
|
||||
import type { Request, Response } from "express-serve-static-core";
|
||||
@@ -14,6 +17,7 @@ import type {
|
||||
ChartDocument,
|
||||
FolderDocument,
|
||||
Playtypes,
|
||||
SongDocument,
|
||||
TableDocument,
|
||||
integer,
|
||||
} from "tachi-common";
|
||||
@@ -23,6 +27,33 @@ const logger = CreateLogCtx(__filename);
|
||||
// Instead of just supporting existing tables, Tachi should also be able
|
||||
// to emit its own, custom BMS tables. These may be dynamic.
|
||||
|
||||
function AppendAndConvertChartsToBMSBody(
|
||||
body: Array<BMSTableEntry>,
|
||||
charts: Array<ChartDocument<"bms:7K" | "bms:14K">>,
|
||||
songMap: Map<integer, SongDocument>,
|
||||
level: string
|
||||
) {
|
||||
for (const chart of charts) {
|
||||
const song = songMap.get(chart.songID);
|
||||
|
||||
// if we've got metadata to add...
|
||||
if (song) {
|
||||
body.push({
|
||||
level,
|
||||
title: song.title,
|
||||
artist: song.artist,
|
||||
md5: chart.data.hashMD5,
|
||||
});
|
||||
} else {
|
||||
logger.warn(`BMS Chart md5=${chart.data.hashMD5} has no parent song.`);
|
||||
body.push({
|
||||
level,
|
||||
md5: chart.data.hashMD5,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a table in Tachi into a bms header.json and body.json.
|
||||
*/
|
||||
@@ -59,32 +90,16 @@ export async function TachiTableToBMSTableJSON(
|
||||
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 we've got metadata to add...
|
||||
if (song) {
|
||||
body.push({
|
||||
level: folder.title,
|
||||
title: song.title,
|
||||
artist: song.artist,
|
||||
md5: chart.data.hashMD5,
|
||||
});
|
||||
} else {
|
||||
logger.warn(`BMS Chart md5=${chart.data.hashMD5} has no parent song.`);
|
||||
body.push({
|
||||
level: folder.title,
|
||||
md5: chart.data.hashMD5,
|
||||
});
|
||||
}
|
||||
}
|
||||
AppendAndConvertChartsToBMSBody(body, charts, songMap, folder.title);
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
export type TachiBMSTable = {
|
||||
playtype: Playtypes["bms"];
|
||||
playtype: Playtypes["bms"] | null; // what playtype is this for? If null, this table
|
||||
// is for all playtypes.
|
||||
|
||||
urlName: string; // what do we call this in the url?
|
||||
tableName: string; // what should it be called in-game?
|
||||
symbol: string; // what symbol should this table have?
|
||||
@@ -93,13 +108,16 @@ export type TachiBMSTable = {
|
||||
forSpecificUser: true; // if this table is user-dependent
|
||||
// like, say, their rivals scores or something.
|
||||
// then the callbacks need to recieve that info.
|
||||
getLevelOrder: (userID: integer) => Promise<Array<string> | undefined>;
|
||||
getBody: (userID: integer) => Promise<Array<BMSTableEntry>>;
|
||||
getLevelOrder: (
|
||||
userID: integer,
|
||||
playtype: Playtypes["bms"]
|
||||
) => Promise<Array<string> | undefined>;
|
||||
getBody: (userID: integer, playtype: Playtypes["bms"]) => Promise<Array<BMSTableEntry>>;
|
||||
}
|
||||
| {
|
||||
forSpecificUser?: false;
|
||||
getLevelOrder: () => Promise<Array<string> | undefined>;
|
||||
getBody: () => Promise<Array<BMSTableEntry>>;
|
||||
getLevelOrder: (playtype: Playtypes["bms"]) => Promise<Array<string> | undefined>;
|
||||
getBody: (playtype: Playtypes["bms"]) => Promise<Array<BMSTableEntry>>;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -136,6 +154,14 @@ function GetUserID(req: Request) {
|
||||
throw new Error(`No userID in params here. Is this route mounted in the right place?`);
|
||||
}
|
||||
|
||||
function GetPlaytype(req: Request) {
|
||||
if ("playtype" in req.params) {
|
||||
return req.params.playtype as Playtypes["bms"];
|
||||
}
|
||||
|
||||
throw new Error(`No playtype in params here. Is this route mounted in the right place?`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a request for a bms table. This endpoint should return "HTML" with the caveat
|
||||
* that atleast one of the lines should refer to a "bmstable" meta header.
|
||||
@@ -170,15 +196,16 @@ export async function HandleBMSTableHeaderRequest(
|
||||
try {
|
||||
let levelOrder;
|
||||
let dataUrl;
|
||||
const playtype = GetPlaytype(req);
|
||||
|
||||
if (bmsTable.forSpecificUser === true) {
|
||||
const userID = GetUserID(req);
|
||||
|
||||
dataUrl = BMSTableToAbsoluteURL(bmsTable, "body", userID);
|
||||
|
||||
levelOrder = await bmsTable.getLevelOrder(userID);
|
||||
levelOrder = await bmsTable.getLevelOrder(userID, playtype);
|
||||
} else {
|
||||
levelOrder = await bmsTable.getLevelOrder();
|
||||
levelOrder = await bmsTable.getLevelOrder(playtype);
|
||||
dataUrl = BMSTableToAbsoluteURL(bmsTable, "body", null);
|
||||
}
|
||||
|
||||
@@ -207,12 +234,14 @@ export async function HandleBMSTableBodyRequest(
|
||||
try {
|
||||
let body;
|
||||
|
||||
const playtype = GetPlaytype(req);
|
||||
|
||||
if (bmsTable.forSpecificUser === true) {
|
||||
const userID = GetUserID(req);
|
||||
|
||||
body = await bmsTable.getBody(userID);
|
||||
body = await bmsTable.getBody(userID, playtype);
|
||||
} else {
|
||||
body = await bmsTable.getBody();
|
||||
body = await bmsTable.getBody(playtype);
|
||||
}
|
||||
|
||||
return res.status(200).send(body);
|
||||
@@ -263,4 +292,69 @@ export const CUSTOM_TACHI_BMS_TABLES: Array<TachiBMSTable> = [
|
||||
return GetFolderNamesInOrder(table);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
urlName: "rival-info",
|
||||
playtype: null,
|
||||
symbol: "Rival",
|
||||
tableName: `${TachiConfig.NAME} Rival Stats`,
|
||||
forSpecificUser: true,
|
||||
async getBody(userID, playtype) {
|
||||
const rivals = await GetRivalUsers(userID, "bms", playtype);
|
||||
|
||||
const body: Array<BMSTableEntry> = [];
|
||||
|
||||
const promises = [];
|
||||
|
||||
for (const rival of rivals) {
|
||||
promises.push(async () => {
|
||||
const scores = await GetRecentUGPTScores(rival.id, "bms", playtype);
|
||||
|
||||
const data = await GetRelevantSongsAndCharts(scores, "bms");
|
||||
const charts = data.charts as unknown as Array<
|
||||
ChartDocument<"bms:7K" | "bms:14K">
|
||||
>;
|
||||
|
||||
const songMap = CreateSongMap(data.songs);
|
||||
|
||||
AppendAndConvertChartsToBMSBody(
|
||||
body,
|
||||
charts,
|
||||
songMap,
|
||||
`${rival.username} Recent Plays`
|
||||
);
|
||||
});
|
||||
|
||||
promises.push(async () => {
|
||||
const scores = await GetRecentUGPTScores(rival.id, "bms", playtype);
|
||||
|
||||
const data = await GetRelevantSongsAndCharts(scores, "bms");
|
||||
const charts = data.charts as unknown as Array<
|
||||
ChartDocument<"bms:7K" | "bms:14K">
|
||||
>;
|
||||
|
||||
const songMap = CreateSongMap(data.songs);
|
||||
|
||||
AppendAndConvertChartsToBMSBody(
|
||||
body,
|
||||
charts,
|
||||
songMap,
|
||||
`${rival.username} Recent Highlights`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
return body;
|
||||
},
|
||||
async getLevelOrder(userID, playtype) {
|
||||
const rivals = await GetRivalUsers(userID, "bms", playtype);
|
||||
|
||||
return rivals.flatMap((rival) => [
|
||||
`${rival.username} Recent Plays`,
|
||||
`${rival.username} Recent Highlights`,
|
||||
]);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ const FindCustomBMSTable: RequestHandler = (req, res, next) => {
|
||||
/**
|
||||
* Return some HTML for this custom table.
|
||||
*
|
||||
* @note Since this is the GPT route, trying to fetch user specific custom tables
|
||||
* @note Since this is the UGPT route, trying to fetch GPT custom tables
|
||||
* will result in a 404. This applies for all subsequent :tableUrlName routes.
|
||||
*
|
||||
* @name GET /api/v1/users/:userID/games/bms/:playtype/custom-tables/:tableUrlName
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
import {
|
||||
GetGoalSummary,
|
||||
GetRecentPlaycount,
|
||||
GetRecentSessions,
|
||||
GetRecentlyViewedFoldersAnyGPT,
|
||||
} from "utils/queries/summary";
|
||||
import { GetUser } from "utils/req-tachi-data";
|
||||
import type { ProfileRatingAlgs } from "utils/string-checks";
|
||||
import { FormatUserDoc, GetAllRankings, GetUserWithID } from "utils/user";
|
||||
import { HashPassword, PasswordCompare, ValidatePassword } from "../../auth/auth";
|
||||
import apiTokensRouter from "./api-tokens/router";
|
||||
import bannerRouter from "./banner/router";
|
||||
import followingRouter from "./following/router";
|
||||
@@ -10,7 +20,6 @@ import { GetUserFromParam, RequireSelfRequestFromUser } from "./middleware";
|
||||
import notifsRouter from "./notifications/router";
|
||||
import pfpRouter from "./pfp/router";
|
||||
import settingsRouter from "./settings/router";
|
||||
import { HashPassword, PasswordCompare, ValidatePassword } from "../../auth/auth";
|
||||
import { Router } from "express";
|
||||
import db from "external/mongo/db";
|
||||
import { GetRecentActivity } from "lib/activity/activity";
|
||||
@@ -21,17 +30,7 @@ import p from "prudence";
|
||||
import prValidate from "server/middleware/prudence-validate";
|
||||
import { DeleteUndefinedProps, IsNonEmptyString, StripUrl } from "utils/misc";
|
||||
import { optNullFluffStrField } from "utils/prudence";
|
||||
import {
|
||||
GetGoalSummary,
|
||||
GetRecentlyViewedFoldersAnyGPT,
|
||||
GetRecentPlaycount,
|
||||
GetRecentSessions,
|
||||
} from "utils/queries/summary";
|
||||
import { GetUser } from "utils/req-tachi-data";
|
||||
import { FormatUserDoc, GetAllRankings, GetUserWithID } from "utils/user";
|
||||
import type { ActivityConstraint } from "lib/activity/activity";
|
||||
import type { IDStrings, ImportTypes, integer, UserGameStats } from "tachi-common";
|
||||
import type { ProfileRatingAlgs } from "utils/string-checks";
|
||||
import type { IDStrings, ImportTypes, UserGameStats, integer } from "tachi-common";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import db from "external/mongo/db";
|
||||
import type { Game, Playtype, integer } from "tachi-common";
|
||||
|
||||
export async function GetRecentUGPTScores(
|
||||
userID: integer,
|
||||
game: Game,
|
||||
playtype: Playtype,
|
||||
limit = 100
|
||||
) {
|
||||
return db.scores.find(
|
||||
{
|
||||
userID,
|
||||
game,
|
||||
playtype,
|
||||
},
|
||||
{
|
||||
sort: {
|
||||
timeAdded: -1,
|
||||
},
|
||||
limit,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function GetRecentUGPTHighlights(
|
||||
userID: integer,
|
||||
game: Game,
|
||||
playtype: Playtype,
|
||||
limit = 100
|
||||
) {
|
||||
return db.scores.find(
|
||||
{
|
||||
userID,
|
||||
game,
|
||||
playtype,
|
||||
highlight: true,
|
||||
},
|
||||
{
|
||||
sort: {
|
||||
timeAdded: -1,
|
||||
},
|
||||
limit,
|
||||
}
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user