Files
zkldi_Tachi/bot/src/webhookHandlers/classUpdate.ts
T
beerpsi 32c5be30bf [chunithm] Update to CHUNITHM VERSE (#1252)
* [chunithm] Update to CHUNITHM VERSE

* fix: change naiveRating to 50 songs

* fix: unfuck duplicate charts caused by later options modifying earlier options

* feat: new rating classes for verse

* fix(bot): chunithm min scores is 50

* add charts to VERSE Omnimix

* add search aliases for new songs

* disable verse intl for now
2025-03-17 18:29:49 +00:00

128 lines
3.4 KiB
TypeScript

import { BotConfig } from "../config";
import { client } from "../main";
import { GetUGPTStats, GetUserInfo } from "../utils/apiRequests";
import { CreateEmbed } from "../utils/embeds";
import { PrependTachiUrl } from "../utils/fetchTachi";
import logger from "../utils/logger";
import { FormatClass, GetGameChannel } from "../utils/misc";
import { FormatGame } from "tachi-common";
import type {
Classes,
GPTString,
Game,
Playtype,
WebhookEventClassUpdateV1,
integer,
} from "tachi-common";
export async function HandleClassUpdateV1(
event: WebhookEventClassUpdateV1["content"]
): Promise<integer> {
const { game, playtype } = event;
let channel;
try {
channel = GetGameChannel(client, game);
} catch (e) {
const err = e as Error;
logger.error(`ClassUpdate handler failed: ${err.message}`);
return 500;
}
if (!ShouldRenderUpdate(game, playtype, event.set, event.new)) {
return 204;
}
const userDoc = await GetUserInfo(event.userID);
// We don't want to render classes if the user is blitzing through them
// because they haven't played enough charts to "average out".
// For example, a new player playing their first 50 charts will blitz through
// atleast 10 of the volforce ranks, which will just result in channel spam.
const minimumNecessaryScores = GetMinimumScores(game, playtype, event.set);
if (minimumNecessaryScores !== null) {
const { totalScores } = await GetUGPTStats(userDoc.id, game, playtype);
// Do not render if the user hasn't hit the score cap.
if (totalScores < minimumNecessaryScores) {
logger.info(`Not rendering class update ${event.set}: ${event.old} -> ${event.new}.`);
return 204;
}
}
const newClass = FormatClass(game, playtype, event.set, event.new);
const embed = CreateEmbed()
.setTitle(`${userDoc.username} just achieved ${newClass} in ${FormatGame(game, playtype)}!`)
.setURL(
`${BotConfig.TACHI_SERVER_LOCATION}/users/${userDoc.username}/games/${game}/${playtype}`
)
.setThumbnail(PrependTachiUrl(`/users/${userDoc.id}/pfp`));
if (event.old !== null) {
embed.setDescription(
`(This was raised from ${FormatClass(game, playtype, event.set, event.old)}.)`
);
}
await channel.send({ embeds: [embed] });
return 200;
}
/**
* Returns Whether this class update is notable enough to be rendered or not.
*/
function ShouldRenderUpdate(
game: Game,
playtype: Playtype,
classSet: Classes[GPTString],
classValue: string
) {
if (game === "sdvx" && classSet === "vfClass") {
return ["IMPERIAL_I", "IMPERIAL_II", "IMPERIAL_III", "IMPERIAL_IV"].includes(classValue);
} else if (game === "popn" && classSet === "class") {
return [
// All of the other classes in pop'n can be trivially blitzed through.
"GOD",
].includes(classValue);
}
return true;
}
function GetMinimumScores(
game: Game,
playtype: Playtype,
classSet: Classes[GPTString]
): integer | null {
if (game === "chunithm" && classSet === "colour") {
return 50;
} else if (game === "sdvx" && classSet === "vfClass") {
return 50;
} else if (game === "gitadora") {
return 50;
} else if (game === "jubeat") {
return 60;
} else if (game === "wacca") {
return 50;
} else if (game === "bms") {
return 20;
} else if (game === "iidx") {
return 20;
} else if (game === "pms") {
return 20;
} else if (game === "ddr") {
return 90;
} else if (game === "maimai" && classSet === "colour") {
return 30;
} else if (game === "maimaidx" && classSet === "colour") {
return 50;
}
return null;
}