show more data on the #1 feed
This commit is contained in:
parent
dd8befa9e0
commit
9fb276ec4e
@ -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;
|
||||||
|
};
|
29
projects/website/src/components/score/badges/full-combo.tsx
Normal file
29
projects/website/src/components/score/badges/full-combo.tsx
Normal file
@ -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>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
Reference in New Issue
Block a user