show more data on the #1 feed
Some checks failed
Deploy Backend / deploy (push) Failing after 1m26s
Deploy Website / deploy (push) Failing after 1m5s

This commit is contained in:
Lee 2024-10-18 07:56:39 +01:00
parent dd8befa9e0
commit 9fb276ec4e
7 changed files with 112 additions and 53 deletions

@ -1,7 +1,7 @@
import ScoreSaberPlayerScoreToken from "@ssr/common/types/token/scoresaber/score-saber-player-score-token"; import ScoreSaberPlayerScoreToken from "@ssr/common/types/token/scoresaber/score-saber-player-score-token";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
import { formatPp } from "@ssr/common/utils/number-utils"; import { formatNumberWithCommas, formatPp } from "@ssr/common/utils/number-utils";
import { isProduction } from "@ssr/common/utils/utils"; import { isProduction } from "@ssr/common/utils/utils";
import { Metadata } from "@ssr/common/types/metadata"; import { Metadata } from "@ssr/common/types/metadata";
import { NotFoundError } from "elysia"; import { NotFoundError } from "elysia";
@ -33,8 +33,14 @@ export class ScoreService {
return; return;
} }
const { score, leaderboard } = playerScore; const { score: scoreToken, leaderboard: leaderboardToken } = playerScore;
const player = score.leaderboardPlayerInfo; const score = getScoreSaberScoreFromToken(scoreToken, leaderboardToken);
const leaderboard = getScoreSaberLeaderboardFromToken(leaderboardToken);
const playerInfo = score.playerInfo;
const player = await scoresaberService.lookupPlayer(playerInfo.id);
if (!player) {
return;
}
// Not ranked // Not ranked
if (leaderboard.stars <= 0) { if (leaderboard.stars <= 0) {
@ -48,15 +54,44 @@ export class ScoreService {
await logToChannel( await logToChannel(
DiscordChannels.numberOneFeed, DiscordChannels.numberOneFeed,
new EmbedBuilder() new EmbedBuilder()
.setTitle(`${player.name} set a #1 on ${leaderboard.songName} ${leaderboard.songSubName}`) .setTitle(`${player.name} just set a #1!`)
.setDescription( .setDescription(
` `${leaderboard.songName} ${leaderboard.songSubName} (${leaderboard.difficulty.difficulty} ${leaderboard.stars.toFixed(2)}★)`
**Player:** https://ssr.fascinated.cc/player/${player.id}
**Leaderboard:** https://ssr.fascinated.cc/leaderboard/${leaderboard.id}
**PP:** ${formatPp(score.pp)}
`
) )
.setThumbnail(leaderboard.coverImage) .addFields([
{
name: "Accuracy",
value: `${score.accuracy.toFixed(2)}%`,
inline: true,
},
{
name: "PP",
value: formatPp(score.pp),
inline: true,
},
{
name: "Player Rank",
value: formatNumberWithCommas(player.rank),
inline: true,
},
{
name: "Misses",
value: formatNumberWithCommas(score.missedNotes),
inline: true,
},
{
name: "Bad Cuts",
value: formatNumberWithCommas(score.badCuts),
inline: true,
},
{
name: "Max Combo",
value: formatNumberWithCommas(score.maxCombo),
inline: true,
},
])
.setThumbnail(leaderboard.songArt)
.setTimestamp(score.timestamp)
.setColor("#00ff00") .setColor("#00ff00")
); );
} }

@ -2,6 +2,7 @@ import Score from "../score";
import { Modifier } from "../modifier"; import { Modifier } from "../modifier";
import ScoreSaberScoreToken from "../../types/token/scoresaber/score-saber-score-token"; import ScoreSaberScoreToken from "../../types/token/scoresaber/score-saber-score-token";
import ScoreSaberLeaderboardPlayerInfoToken from "../../types/token/scoresaber/score-saber-leaderboard-player-info-token"; import ScoreSaberLeaderboardPlayerInfoToken from "../../types/token/scoresaber/score-saber-leaderboard-player-info-token";
import ScoreSaberLeaderboardToken from "@ssr/types/token/scoresaber/score-saber-leaderboard-token";
export default interface ScoreSaberScore extends Score { export default interface ScoreSaberScore extends Score {
/** /**
@ -21,6 +22,11 @@ export default interface ScoreSaberScore extends Score {
*/ */
readonly weight?: number; readonly weight?: number;
/**
* The max combo of the score.
*/
readonly maxCombo: number;
/** /**
* The player who set the score * The player who set the score
*/ */
@ -31,8 +37,12 @@ export default interface ScoreSaberScore extends Score {
* Gets a {@link ScoreSaberScore} from a {@link ScoreSaberScoreToken}. * Gets a {@link ScoreSaberScore} from a {@link ScoreSaberScoreToken}.
* *
* @param token the token to convert * @param token the token to convert
* @param leaderboard the leaderboard the score was set on
*/ */
export function getScoreSaberScoreFromToken(token: ScoreSaberScoreToken): ScoreSaberScore { export function getScoreSaberScoreFromToken(
token: ScoreSaberScoreToken,
leaderboard?: ScoreSaberLeaderboardToken
): ScoreSaberScore {
const modifiers: Modifier[] = const modifiers: Modifier[] =
token.modifiers == undefined || token.modifiers === "" token.modifiers == undefined || token.modifiers === ""
? [] ? []
@ -48,15 +58,18 @@ export function getScoreSaberScoreFromToken(token: ScoreSaberScoreToken): ScoreS
return { return {
leaderboard: "scoresaber", leaderboard: "scoresaber",
score: token.baseScore, score: token.baseScore,
accuracy: leaderboard ? token.baseScore / leaderboard.maxScore : Infinity,
rank: token.rank, rank: token.rank,
modifiers: modifiers, modifiers: modifiers,
misses: token.missedNotes, misses: token.missedNotes + token.badCuts,
missedNotes: token.missedNotes,
badCuts: token.badCuts, badCuts: token.badCuts,
fullCombo: token.fullCombo, fullCombo: token.fullCombo,
timestamp: new Date(token.timeSet), timestamp: new Date(token.timeSet),
id: token.id, id: token.id,
pp: token.pp, pp: token.pp,
weight: token.weight, weight: token.weight,
maxCombo: token.maxCombo,
playerInfo: token.leaderboardPlayerInfo, playerInfo: token.leaderboardPlayerInfo,
}; };
} }

@ -13,6 +13,11 @@ export default interface Score {
*/ */
readonly score: number; readonly score: number;
/**
* The accuracy of the score.
*/
readonly accuracy: number;
/** /**
* The rank for the score. * The rank for the score.
* @private * @private
@ -26,11 +31,16 @@ export default interface Score {
readonly modifiers: Modifier[]; readonly modifiers: Modifier[];
/** /**
* The amount missed notes. * The amount total amount of misses.
* @private * @private
*/ */
readonly misses: number; readonly misses: number;
/**
* The amount of missed notes.
*/
readonly missedNotes: number;
/** /**
* The amount of bad cuts. * The amount of bad cuts.
* @private * @private

@ -1,11 +1,9 @@
import { formatNumberWithCommas } from "@ssr/common/utils/number-utils";
import { XMarkIcon } from "@heroicons/react/24/solid";
import clsx from "clsx";
import { getScoreBadgeFromAccuracy } from "@/common/song-utils"; import { getScoreBadgeFromAccuracy } from "@/common/song-utils";
import Tooltip from "@/components/tooltip"; import Tooltip from "@/components/tooltip";
import { ScoreBadge, ScoreBadges } from "@/components/score/score-badge"; import { ScoreBadge, ScoreBadges } from "@/components/score/score-badge";
import ScoreSaberScore from "@ssr/common/score/impl/scoresaber-score"; import ScoreSaberScore from "@ssr/common/score/impl/scoresaber-score";
import ScoreSaberLeaderboard from "@ssr/common/leaderboard/impl/scoresaber-leaderboard"; import ScoreSaberLeaderboard from "@ssr/common/leaderboard/impl/scoresaber-leaderboard";
import FullComboBadge from "@/components/score/badges/full-combo";
const badges: ScoreBadge[] = [ const badges: ScoreBadge[] = [
{ {
@ -57,14 +55,7 @@ const badges: ScoreBadge[] = [
{ {
name: "Full Combo", name: "Full Combo",
create: (score: ScoreSaberScore) => { create: (score: ScoreSaberScore) => {
const fullCombo = score.misses === 0; return <FullComboBadge score={score} />;
return (
<>
<p>{fullCombo ? <span className="text-green-400">FC</span> : formatNumberWithCommas(score.misses)}</p>
<XMarkIcon className={clsx("w-5 h-5", fullCombo ? "hidden" : "text-red-400")} />
</>
);
}, },
}, },
]; ];

@ -0,0 +1,8 @@
import ScoreSaberScore from "@ssr/common/score/impl/scoresaber-score";
export type ScoreBadgeProps = {
/**
* The score for this badge
*/
score: ScoreSaberScore;
};

@ -0,0 +1,29 @@
import { formatNumberWithCommas } from "@ssr/common/utils/number-utils";
import { XMarkIcon } from "@heroicons/react/24/solid";
import clsx from "clsx";
import Tooltip from "@/components/tooltip";
import { ScoreBadgeProps } from "@/components/score/badges/badge-props";
export default function FullComboBadge({ score }: ScoreBadgeProps) {
return (
<Tooltip
display={
<div className="flex flex-col justify-center items-center">
{!score.fullCombo ? (
<>
<p>Missed Notes: {formatNumberWithCommas(score.missedNotes)}</p>
<p>Bad Cuts: {formatNumberWithCommas(score.badCuts)}</p>
</>
) : (
<p>Full Combo</p>
)}
</div>
}
>
<div className="flex gap-1">
<p>{score.fullCombo ? <span className="text-green-400">FC</span> : formatNumberWithCommas(score.misses)}</p>
<XMarkIcon className={clsx("w-5 h-5", score.fullCombo ? "hidden" : "text-red-400")} />
</div>
</Tooltip>
);
}

@ -1,11 +1,10 @@
import { formatNumberWithCommas, formatPp } from "@ssr/common/utils/number-utils"; import { formatNumberWithCommas, formatPp } from "@ssr/common/utils/number-utils";
import { getScoreBadgeFromAccuracy } from "@/common/song-utils"; import { getScoreBadgeFromAccuracy } from "@/common/song-utils";
import { XMarkIcon } from "@heroicons/react/24/solid";
import clsx from "clsx";
import Tooltip from "@/components/tooltip"; import Tooltip from "@/components/tooltip";
import { ScoreBadge, ScoreBadges } from "@/components/score/score-badge"; import { ScoreBadge, ScoreBadges } from "@/components/score/score-badge";
import ScoreSaberScore from "@ssr/common/score/impl/scoresaber-score"; import ScoreSaberScore from "@ssr/common/score/impl/scoresaber-score";
import ScoreSaberLeaderboard from "@ssr/common/leaderboard/impl/scoresaber-leaderboard"; import ScoreSaberLeaderboard from "@ssr/common/leaderboard/impl/scoresaber-leaderboard";
import FullComboBadge from "@/components/score/badges/full-combo";
const badges: ScoreBadge[] = [ const badges: ScoreBadge[] = [
{ {
@ -88,33 +87,7 @@ const badges: ScoreBadge[] = [
{ {
name: "Full Combo", name: "Full Combo",
create: (score: ScoreSaberScore) => { create: (score: ScoreSaberScore) => {
return ( return <FullComboBadge score={score} />;
<Tooltip
display={
<div className="flex flex-col justify-center items-center">
{!score.fullCombo ? (
<>
<p>Missed Notes: {formatNumberWithCommas(score.misses)}</p>
<p>Bad Cuts: {formatNumberWithCommas(score.badCuts)}</p>
</>
) : (
<p>Full Combo</p>
)}
</div>
}
>
<div className="flex gap-1">
<p>
{score.fullCombo ? (
<span className="text-green-400">FC</span>
) : (
formatNumberWithCommas(score.misses + score.badCuts)
)}
</p>
<XMarkIcon className={clsx("w-5 h-5", score.fullCombo ? "hidden" : "text-red-400")} />
</div>
</Tooltip>
);
}, },
}, },
]; ];