This repository has been archived on 2024-10-29. You can view files and clone it, but cannot push or open issues or pull requests.
scoresaber-reloadedv3/projects/backend/src/service/player.service.ts

196 lines
6.8 KiB
TypeScript
Raw Normal View History

import { PlayerDocument, PlayerModel } from "@ssr/common/model/player";
2024-10-09 01:17:00 +01:00
import { NotFoundError } from "../error/not-found-error";
import { getDaysAgoDate, getMidnightAlignedDate } from "@ssr/common/utils/time-utils";
import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
import ScoreSaberPlayerToken from "@ssr/common/types/token/scoresaber/score-saber-player-token";
2024-10-09 15:25:21 +01:00
import { InternalServerError } from "../error/internal-server-error";
2024-10-15 04:09:47 +01:00
import ScoreSaberPlayerScoreToken from "@ssr/common/types/token/scoresaber/score-saber-player-score-token";
2024-10-16 07:47:52 +01:00
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import { formatPp } from "@ssr/common/utils/number-utils";
2024-10-16 08:21:27 +01:00
import { isProduction } from "@ssr/common/utils/utils";
import { DiscordChannels, logToChannel } from "../bot/bot";
import { EmbedBuilder } from "discord.js";
2024-10-09 01:17:00 +01:00
export class PlayerService {
/**
* Get a player from the database.
*
* @param id the player to fetch
* @param create if true, create the player if it doesn't exist
2024-10-12 02:36:44 +01:00
* @param playerToken an optional player token for the player
2024-10-09 01:17:00 +01:00
* @returns the player
* @throws NotFoundError if the player is not found
*/
2024-10-12 02:36:44 +01:00
public static async getPlayer(
id: string,
create: boolean = false,
playerToken?: ScoreSaberPlayerToken
): Promise<PlayerDocument> {
2024-10-09 01:17:00 +01:00
let player: PlayerDocument | null = await PlayerModel.findById(id);
if (player === null) {
2024-10-11 01:12:27 +01:00
// If create is on, create the player, otherwise return unknown player
2024-10-12 02:36:44 +01:00
playerToken = create ? (playerToken ? playerToken : await scoresaberService.lookupPlayer(id)) : undefined;
2024-10-09 01:17:00 +01:00
if (playerToken === undefined) {
throw new NotFoundError(`Player "${id}" not found`);
}
console.log(`Creating player "${id}"...`);
try {
player = (await PlayerModel.create({ _id: id })) as PlayerDocument;
player.trackedSince = new Date();
await this.seedPlayerHistory(player, playerToken);
2024-10-16 08:21:27 +01:00
// Only notify in production
if (isProduction()) {
2024-10-17 19:02:32 +01:00
await logToChannel(
DiscordChannels.trackedPlayerLogs,
new EmbedBuilder()
.setTitle("New Player Tracked")
.setDescription(`https://ssr.fascinated.cc/player/${playerToken.id}`)
.addFields([
{
name: "Username",
value: playerToken.name,
inline: true,
},
{
name: "ID",
value: playerToken.id,
inline: true,
},
{
name: "PP",
value: formatPp(playerToken.pp) + "pp",
inline: true,
},
])
.setThumbnail(playerToken.profilePicture)
.setColor("#00ff00")
);
2024-10-16 08:21:27 +01:00
}
} catch (err) {
const message = `Failed to create player document for "${id}"`;
console.log(message, err);
throw new InternalServerError(message);
2024-10-09 01:17:00 +01:00
}
}
return player;
}
/**
* Seeds the player's history using data from
* the ScoreSaber API.
*
* @param player the player to seed
* @param playerToken the SoreSaber player token
*/
public static async seedPlayerHistory(player: PlayerDocument, playerToken: ScoreSaberPlayerToken): Promise<void> {
// Loop through rankHistory in reverse, from current day backwards
const playerRankHistory = playerToken.histories.split(",").map((value: string) => {
return parseInt(value);
});
playerRankHistory.push(playerToken.rank);
let daysAgo = 1; // Start from yesterday
for (let i = playerRankHistory.length - daysAgo - 1; i >= 0; i--) {
const rank = playerRankHistory[i];
const date = getMidnightAlignedDate(getDaysAgoDate(daysAgo));
player.setStatisticHistory(date, {
rank: rank,
});
daysAgo += 1; // Increment daysAgo for each earlier rank
}
2024-10-16 11:40:46 +01:00
player.markModified("statisticHistory");
2024-10-09 01:17:00 +01:00
await player.save();
}
/**
* Tracks a players statistics
*
* @param foundPlayer the player to track
* @param playerToken an optional player token
2024-10-09 01:17:00 +01:00
*/
public static async trackScoreSaberPlayer(
foundPlayer: PlayerDocument,
playerToken?: ScoreSaberPlayerToken
): Promise<void> {
2024-10-10 01:40:21 +01:00
const dateToday = getMidnightAlignedDate(new Date());
const player = playerToken ? playerToken : await scoresaberService.lookupPlayer(foundPlayer.id);
2024-10-09 01:17:00 +01:00
if (player == undefined) {
console.log(`Player "${foundPlayer.id}" not found on ScoreSaber`);
return;
}
if (player.inactive) {
console.log(`Player "${foundPlayer.id}" is inactive on ScoreSaber`);
return;
}
// Seed the history with ScoreSaber data if no history exists
if (foundPlayer.getDaysTracked() === 0) {
await this.seedPlayerHistory(foundPlayer, player);
}
// Update current day's statistics
let history = foundPlayer.getHistoryByDate(dateToday);
if (history == undefined) {
history = {}; // Initialize if history is not found
}
// Set the history data
history.pp = player.pp;
history.countryRank = player.countryRank;
history.rank = player.rank;
2024-10-11 01:06:02 +01:00
history.accuracy = {
averageRankedAccuracy: player.scoreStats.averageRankedAccuracy,
};
2024-10-09 01:17:00 +01:00
foundPlayer.setStatisticHistory(dateToday, history);
foundPlayer.sortStatisticHistory();
foundPlayer.lastTracked = new Date();
2024-10-16 11:36:06 +01:00
foundPlayer.markModified("statisticHistory");
2024-10-09 01:17:00 +01:00
await foundPlayer.save();
console.log(`Tracked player "${foundPlayer.id}"!`);
}
2024-10-15 04:09:47 +01:00
/**
* Track player score.
*
* @param score the score to track
* @param leaderboard the leaderboard to track
*/
public static async trackScore({ score, leaderboard }: ScoreSaberPlayerScoreToken) {
const playerId = score.leaderboardPlayerInfo.id;
2024-10-15 18:59:13 +01:00
const playerName = score.leaderboardPlayerInfo.name;
2024-10-15 04:09:47 +01:00
const player: PlayerDocument | null = await PlayerModel.findById(playerId);
// Player is not tracked, so ignore the score.
if (player == undefined) {
return;
}
const today = new Date();
let history = player.getHistoryByDate(today);
if (history == undefined || Object.keys(history).length === 0) {
history = { scores: { rankedScores: 0, unrankedScores: 0 } }; // Ensure initialization
}
const scores = history.scores || {};
if (leaderboard.stars > 0) {
scores.rankedScores!++;
} else {
scores.unrankedScores!++;
}
history.scores = scores;
player.setStatisticHistory(today, history);
player.sortStatisticHistory();
// Save the changes
player.markModified("statisticHistory");
await player.save();
console.log(
2024-10-15 18:59:13 +01:00
`Updated scores set statistic for "${playerName}"(${playerId}), scores today: ${scores.rankedScores} ranked, ${scores.unrankedScores} unranked`
2024-10-15 04:09:47 +01:00
);
}
2024-10-09 01:17:00 +01:00
}