remove BL data (for now until I can come up with a better soloution)
All checks were successful
deploy / deploy (push) Successful in 59s

This commit is contained in:
Lee 2023-10-22 13:50:05 +01:00
parent 80e6c0da43
commit 2b91388b95
19 changed files with 28 additions and 742 deletions

@ -1,6 +1,5 @@
"use client"; "use client";
import { useBeatLeaderScoresStore } from "@/store/beatLeaderScoresStore";
import { useScoresaberScoresStore } from "@/store/scoresaberScoresStore"; import { useScoresaberScoresStore } from "@/store/scoresaberScoresStore";
type AppProviderProps = { type AppProviderProps = {
@ -13,11 +12,6 @@ export function AppProvider({ children }: AppProviderProps) {
const UPDATE_INTERVAL = 1000 * 60 * 15; // 15 minutes const UPDATE_INTERVAL = 1000 * 60 * 15; // 15 minutes
useBeatLeaderScoresStore.getState().updatePlayerScores();
setInterval(() => {
useBeatLeaderScoresStore.getState().updatePlayerScores();
}, UPDATE_INTERVAL);
useScoresaberScoresStore.getState().updatePlayerScores(); useScoresaberScoresStore.getState().updatePlayerScores();
setInterval(() => { setInterval(() => {
useScoresaberScoresStore.getState().updatePlayerScores(); useScoresaberScoresStore.getState().updatePlayerScores();

@ -1,5 +1,4 @@
import { ScoresaberPlayer } from "@/schemas/scoresaber/player"; import { ScoresaberPlayer } from "@/schemas/scoresaber/player";
import { useBeatLeaderScoresStore } from "@/store/beatLeaderScoresStore";
import { useScoresaberScoresStore } from "@/store/scoresaberScoresStore"; import { useScoresaberScoresStore } from "@/store/scoresaberScoresStore";
import { useSettingsStore } from "@/store/settingsStore"; import { useSettingsStore } from "@/store/settingsStore";
import { formatNumber } from "@/utils/number"; import { formatNumber } from "@/utils/number";
@ -51,7 +50,6 @@ export default function PlayerInfo({ playerData }: PlayerInfoProps) {
} }
async function addProfile(isFriend: boolean) { async function addProfile(isFriend: boolean) {
const setupScoresaber = async () => {
if (!useScoresaberScoresStore.getState().exists(playerId)) { if (!useScoresaberScoresStore.getState().exists(playerId)) {
const reponse = await playerScoreStore?.addPlayer( const reponse = await playerScoreStore?.addPlayer(
playerId, playerId,
@ -60,7 +58,7 @@ export default function PlayerInfo({ playerData }: PlayerInfoProps) {
if (page == 1) { if (page == 1) {
toastId.current = toast.info( toastId.current = toast.info(
`Fetching ScoreSaber scores ${page}/${totalPages}`, `Fetching scores ${page}/${totalPages}`,
{ {
autoClose: autoClose, autoClose: autoClose,
progress: page / totalPages, progress: page / totalPages,
@ -69,13 +67,13 @@ export default function PlayerInfo({ playerData }: PlayerInfoProps) {
} else { } else {
toast.update(toastId.current, { toast.update(toastId.current, {
progress: page / totalPages, progress: page / totalPages,
render: `Fetching ScoreSaber scores ${page}/${totalPages}`, render: `Fetching scores ${page}/${totalPages}`,
autoClose: autoClose, autoClose: autoClose,
}); });
} }
console.log( console.log(
`Fetching ScoreSaber scores for ${playerId} (${page}/${totalPages})`, `Fetching scores for ${playerId} (${page}/${totalPages})`,
); );
}, },
); );
@ -85,46 +83,6 @@ export default function PlayerInfo({ playerData }: PlayerInfoProps) {
return; return;
} }
} }
};
const setupBeatleader = async () => {
if (!useBeatLeaderScoresStore.getState().exists(playerId)) {
const reponse = await playerScoreStore?.addPlayer(
playerId,
(page, totalPages) => {
const autoClose = page == totalPages ? 5000 : false;
if (page == 1) {
toastId.current = toast.info(
`Fetching BeatLeader scores ${page}/${totalPages}`,
{
autoClose: autoClose,
progress: page / totalPages,
},
);
} else {
toast.update(toastId.current, {
progress: page / totalPages,
render: `Fetching BeatLeader scores ${page}/${totalPages}`,
autoClose: autoClose,
});
}
console.log(
`Fetching BeatLeader scores for ${playerId} (${page}/${totalPages})`,
);
},
);
if (reponse?.error) {
toast.error("Failed to fetch scores");
console.log(reponse.message);
return;
}
}
};
await setupScoresaber();
await setupBeatleader();
if (!isFriend) { if (!isFriend) {
toast.success(`Successfully set ${playerData.name} as your profile`); toast.success(`Successfully set ${playerData.name} as your profile`);

@ -1,7 +1,6 @@
import { ScoresaberLeaderboardInfo } from "@/schemas/scoresaber/leaderboard"; import { ScoresaberLeaderboardInfo } from "@/schemas/scoresaber/leaderboard";
import { ScoresaberPlayer } from "@/schemas/scoresaber/player"; import { ScoresaberPlayer } from "@/schemas/scoresaber/player";
import { ScoresaberScore } from "@/schemas/scoresaber/score"; import { ScoresaberScore } from "@/schemas/scoresaber/score";
import { useBeatLeaderScoresStore } from "@/store/beatLeaderScoresStore";
import { formatNumber } from "@/utils/number"; import { formatNumber } from "@/utils/number";
import { import {
CheckIcon, CheckIcon,
@ -21,11 +20,6 @@ type ScoreProps = {
export default function Score({ score, player, leaderboard }: ScoreProps) { export default function Score({ score, player, leaderboard }: ScoreProps) {
const isFullCombo = score.missedNotes + score.badCuts === 0; const isFullCombo = score.missedNotes + score.badCuts === 0;
const beatleaderScoreData = useBeatLeaderScoresStore
.getState()
.getScore(player.id, leaderboard.songHash);
console.log(beatleaderScoreData);
return ( return (
<div className="grid grid-cols-1 pb-2 pt-2 first:pt-0 last:pb-0 md:grid-cols-[1.1fr_6fr_3fr] xl:md:grid-cols-[.95fr_6fr_3fr]"> <div className="grid grid-cols-1 pb-2 pt-2 first:pt-0 last:pb-0 md:grid-cols-[1.1fr_6fr_3fr] xl:md:grid-cols-[.95fr_6fr_3fr]">

@ -1,30 +0,0 @@
import { BeatleaderModifierRating } from "./modifierRating";
import { BeatleaderModifier } from "./modifiers";
export type BeatleaderDifficulty = {
id: number;
value: number;
mode: number;
difficultyName: string;
modeName: string;
status: number;
modifierValues: BeatleaderModifier;
modifiersRating: BeatleaderModifierRating;
nominatedTime: number;
qualifiedTime: number;
rankedTime: number;
stars: number;
predictedAcc: number;
passRating: number;
accRating: number;
techRating: number;
type: number;
njs: number;
nps: number;
notes: number;
bombs: number;
walls: number;
maxScore: number;
duration: number;
requirements: number;
};

@ -1,16 +0,0 @@
import { BeatleaderDifficulty } from "./difficulty";
import { BeatleaderSong } from "./song";
export type BeatleaderLeaderboard = {
id: string;
song: BeatleaderSong;
difficulty: BeatleaderDifficulty;
scores: null; // ??
changes: null; // ??
qualification: null; // ??
reweight: null; // ??
leaderboardGroup: null; // ??
plays: number;
clan: null; // ??
clanRankingContested: boolean;
};

@ -1,5 +0,0 @@
export type BeatleaderMetadata = {
itemsPerPage: number;
page: number;
total: number;
};

@ -1,18 +0,0 @@
export type BeatleaderModifierRating = {
id: number;
fsPredictedAcc: number;
fsPassRating: number;
fsAccRating: number;
fsTechRating: number;
fsStars: number;
ssPredictedAcc: number;
ssPassRating: number;
ssAccRating: number;
ssTechRating: number;
ssStars: number;
sfPredictedAcc: number;
sfPassRating: number;
sfAccRating: number;
sfTechRating: number;
sfStars: number;
};

@ -1,16 +0,0 @@
export type BeatleaderModifier = {
modifierId: number;
da: number;
fs: number;
sf: number;
ss: number;
gn: number;
na: number;
nb: number;
nf: number;
no: number;
pm: number;
sc: number;
sa: number;
op: number;
};

@ -1,51 +0,0 @@
import { BeatleaderLeaderboard } from "./leaderboard";
import { BeatleaderScoreImprovement } from "./scoreImprovement";
import { BeatleaderScoreOffsets } from "./scoreOffsets";
export type BeatleaderScore = {
myScore: null; // ??
validContexts: number;
leaderboard: BeatleaderLeaderboard;
contextExtensions: null; // ??
accLeft: number;
accRight: number;
id: number;
baseScore: number;
modifiedScore: number;
accuracy: number;
playerId: string;
pp: number;
bonusPp: number;
passPP: number;
accPP: number;
techPP: number;
rank: number;
country: string;
fcAccuracy: number;
fcPp: number;
weight: number;
replay: string;
modifiers: string;
badCuts: number;
missedNotes: number;
bombCuts: number;
wallsHit: number;
pauses: number;
fullCombo: boolean;
platform: string;
maxCombo: number;
maxStreak: number;
hmd: number;
controller: number;
leaderboardId: string;
timeset: string;
timepost: number;
replaysWatched: number;
playCount: number;
priority: number;
player: null; // ??
scoreImprovement: BeatleaderScoreImprovement;
rankVoting: null; // ??
metadata: null; // ??
offsets: BeatleaderScoreOffsets;
};

@ -1,19 +0,0 @@
export type BeatleaderScoreImprovement = {
id: number;
timeset: number;
score: number;
accuracy: number;
pp: number;
bonusPp: number;
rank: number;
accRight: number;
accLeft: number;
averageRankedAccuracy: number;
totalPp: number;
totalRank: number;
badCuts: number;
missedNotes: number;
bombCuts: number;
wallsHit: number;
pauses: number;
};

@ -1,8 +0,0 @@
export type BeatleaderScoreOffsets = {
id: number;
frames: number;
notes: number;
walls: number;
heights: number;
pauses: number;
};

@ -1,5 +0,0 @@
import { BeatleaderSmallerSong } from "./smallerSong";
export type BeatleaderSmallerLeaderboard = {
song: BeatleaderSmallerSong;
};

@ -1,14 +0,0 @@
import { BeatleaderSmallerLeaderboard } from "./smallerLeaderboard";
import { BeatleaderSmallerScoreImprovement } from "./smallerScoreImprovement";
export type BeatleaderSmallerScore = {
id: number;
timepost: number;
accLeft: number;
accRight: number;
fcAccuracy: number;
wallsHit: number;
replay: string;
leaderboard: BeatleaderSmallerLeaderboard;
scoreImprovement: BeatleaderSmallerScoreImprovement | null;
};

@ -1,9 +0,0 @@
export type BeatleaderSmallerScoreImprovement = {
score: number;
accuracy: number;
accRight: number;
accLeft: number;
badCuts: number;
missedNotes: number;
bombCuts: number;
};

@ -1,4 +0,0 @@
export type BeatleaderSmallerSong = {
hash: string;
bpm: number;
};

@ -1,16 +0,0 @@
export type BeatleaderSong = {
id: string;
hash: string;
name: string;
subName: string;
author: string;
mapperId: string;
coverImage: string;
fullCoverImage: string;
downloadUrl: string;
bpm: number;
duration: number;
tags: string;
uploadTime: number;
difficulties: null; // ??
};

@ -1,347 +0,0 @@
"use client";
import { BeatleaderSmallerScore } from "@/schemas/beatleader/smaller/smallerScore";
import { ScoresaberPlayer } from "@/schemas/scoresaber/player";
import { BeatLeaderAPI } from "@/utils/beatleader/api";
import moment from "moment";
import { toast } from "react-toastify";
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";
import { useSettingsStore } from "./settingsStore";
type Player = {
id: string;
scores: BeatleaderSmallerScore[];
};
interface BeatLeaderScoresStore {
lastUpdated: number;
players: Player[];
/**
* Sets when the player scores were last updated
*
* @param lastUpdated when the player scores were last updated
*/
setLastUpdated: (lastUpdated: number) => void;
/**
* Checks if the player exists
*
* @param playerId the player id
* @returns if the player exists
*/
exists: (playerId: string) => boolean;
/**
* Gets the given player
*
* @param playerId the player id
* @returns the player
*/
get(playerId: string): Player | undefined;
/**
* Gets the score for the given player and song hash
*
* @param playerId the player id
* @param songHash the song hash
*/
getScore(
playerId: string,
songHash: string,
): BeatleaderSmallerScore | undefined;
/**
* Adds the player to the local database
*
* @param playerId the player id
* @param callback a callback to call when a score page is fetched
* @returns if the player was added successfully
*/
addPlayer: (
playerId: string,
callback?: (page: number, totalPages: number) => void,
) => Promise<{
error: boolean;
message: string;
}>;
/**
* Refreshes the player scores and adds any new scores to the local database
*/
updatePlayerScores: () => void;
}
const UPDATE_INTERVAL = 1000 * 60 * 30; // 30 minutes
export const useBeatLeaderScoresStore = create<BeatLeaderScoresStore>()(
persist(
(set) => ({
lastUpdated: 0,
players: [],
setLastUpdated: (lastUpdated: number) => {
set({ lastUpdated });
},
exists: (playerId: string) => {
const players: Player[] = useBeatLeaderScoresStore.getState().players;
return players.some((player) => player.id == playerId);
},
get: (playerId: string) => {
const players: Player[] = useBeatLeaderScoresStore.getState().players;
return players.find((player) => player.id == playerId);
},
getScore: (playerId: string, songHash: string) => {
const player = useBeatLeaderScoresStore.getState().get(playerId);
if (player == undefined) return undefined;
return player.scores.find(
(score) => score.leaderboard.song.hash == songHash,
);
},
addPlayer: async (
playerId: string,
callback?: (
page: number,
totalPages: number,
leaderboardName: string,
) => void,
) => {
const players = useBeatLeaderScoresStore.getState().players;
// Check if the player already exists
if (useBeatLeaderScoresStore.getState().exists(playerId)) {
return {
error: true,
message: "Player already exists",
};
}
// Get all of the players scores
const scores = await BeatLeaderAPI.fetchAllScores(
playerId,
(page, totalPages) => {
if (callback) callback(page, totalPages, "BeatLeader");
},
);
if (scores == undefined) {
return {
error: true,
message: "Could not fetch beatleader scores for player",
};
}
let smallerScores = new Array<BeatleaderSmallerScore>();
for (const score of scores) {
// We have to do this to limit the amount of data we store
// so we don't exceed the local storage limit
smallerScores.push({
id: score.id,
accLeft: score.accLeft,
accRight: score.accRight,
fcAccuracy: score.fcAccuracy,
wallsHit: score.wallsHit,
replay: score.replay,
leaderboard: {
song: {
bpm: score.leaderboard.song.bpm,
hash: score.leaderboard.song.hash,
},
},
scoreImprovement:
score.scoreImprovement != null
? {
score: score.scoreImprovement.score,
accuracy: score.scoreImprovement.accuracy,
accRight: score.scoreImprovement.accRight,
accLeft: score.scoreImprovement.accLeft,
badCuts: score.scoreImprovement.badCuts,
missedNotes: score.scoreImprovement.missedNotes,
bombCuts: score.scoreImprovement.bombCuts,
}
: null,
timepost: score.timepost,
});
}
// Remove scores that are already in the database
const player = useBeatLeaderScoresStore.getState().get(playerId);
if (player) {
smallerScores = smallerScores.filter(
(score) => player.scores.findIndex((s) => s.id == score.id) == -1,
);
}
set({
lastUpdated: Date.now(),
players: [
...players,
{
id: playerId,
scores: smallerScores,
},
],
});
return {
error: false,
message: "Player added successfully",
};
},
updatePlayerScores: async () => {
const players = useBeatLeaderScoresStore.getState().players;
const friends = useSettingsStore.getState().friends;
let allPlayers = new Array<ScoresaberPlayer>();
for (const friend of friends) {
allPlayers.push(friend);
}
const localPlayer = useSettingsStore.getState().player;
if (localPlayer) {
allPlayers.push(localPlayer);
}
// add local player and friends if they don't exist
for (const player of allPlayers) {
if (useBeatLeaderScoresStore.getState().get(player.id) == undefined) {
toast.info(
`${
player.id == localPlayer?.id
? `You were`
: `Friend ${player.name} was`
} missing from the BeatLeader scores database, adding...`,
);
await useBeatLeaderScoresStore.getState().addPlayer(player.id);
toast.success(
`${
player.id == useSettingsStore.getState().player?.id
? `You were`
: `Friend ${player.name} was`
} added to the BeatLeader scores database`,
);
}
}
// Skip if we refreshed the scores recently
const timeUntilRefreshMs =
UPDATE_INTERVAL -
(Date.now() - useBeatLeaderScoresStore.getState().lastUpdated);
if (timeUntilRefreshMs > 0) {
console.log(
"Waiting",
moment.duration(timeUntilRefreshMs).humanize(),
"to refresh scores for players",
);
setTimeout(
() => useBeatLeaderScoresStore.getState().updatePlayerScores(),
timeUntilRefreshMs,
);
return;
}
// loop through all of the players and update their scores
for (const player of players) {
if (player == undefined) continue;
console.log(`Updating scores for ${player.id}...`);
let newPlayers = players;
let oldScores = player.scores;
// Sort the scores by date (newset to oldest), so we know when to stop searching for new scores
oldScores = oldScores.sort((a, b) => {
return a.timepost - b.timepost;
});
if (!oldScores.length) return;
const mostRecentScore = oldScores[0];
let search = true;
let page = 0;
let newScoresCount = 0;
while (search) {
page++;
const newScores = await BeatLeaderAPI.fetchScores(player.id, page);
if (newScores == undefined) continue;
for (const score of newScores.scores) {
if (mostRecentScore && score.id == mostRecentScore.id) {
search = false;
break;
}
// remove the old score
const oldScoreIndex = oldScores.findIndex(
(score) => score.id == score.id,
);
if (oldScoreIndex != -1) {
oldScores = oldScores.splice(oldScoreIndex, 1);
}
oldScores.push({
id: score.id,
accLeft: score.accLeft,
accRight: score.accRight,
fcAccuracy: score.fcAccuracy,
wallsHit: score.wallsHit,
replay: score.replay,
leaderboard: {
song: {
bpm: score.leaderboard.song.bpm,
hash: score.leaderboard.song.hash,
},
},
scoreImprovement:
score.scoreImprovement != null
? {
score: score.scoreImprovement.score,
accuracy: score.scoreImprovement.accuracy,
accRight: score.scoreImprovement.accRight,
accLeft: score.scoreImprovement.accLeft,
badCuts: score.scoreImprovement.badCuts,
missedNotes: score.scoreImprovement.missedNotes,
bombCuts: score.scoreImprovement.bombCuts,
}
: null,
timepost: score.timepost,
});
newScoresCount++;
}
}
// Remove the player if it already exists
newPlayers = newPlayers.filter((playerr) => playerr.id != player.id);
// Add the player
newPlayers.push({
id: player.id,
scores: oldScores,
});
if (newScoresCount > 0) {
console.log(
`Found ${newScoresCount} new beatleader scores for ${player.id}`,
);
}
set({
players: newPlayers,
lastUpdated: Date.now(),
});
console.log(friends);
}
},
}),
{
name: "beatleaderScores",
storage: createJSONStorage(() => localStorage),
version: 2,
migrate: (state: any, version: number) => {
state.scores = [];
return state;
},
},
),
);

@ -1,102 +0,0 @@
import { BeatleaderScore } from "@/schemas/beatleader/score";
import { ssrSettings } from "@/ssrSettings";
import { FetchQueue } from "../fetchWithQueue";
import { formatString } from "../string";
// Create a fetch instance with a cache
const fetchQueue = new FetchQueue();
// Api endpoints
const API_URL = ssrSettings.proxy + "/https://api.beatleader.xyz";
const PLAYER_SCORES_URL =
API_URL + "/player/{}/scores?sortBy=date&order=0&page={}&count=100";
/**
* Get the players scores from the given page
*
* @param playerId the id of the player
* @param page the page to get the scores from
* @param searchType the type of search to perform
* @param limit the limit of scores to get
* @returns a list of scores
*/
async function fetchScores(
playerId: string,
page: number = 1,
limit: number = 100,
): Promise<
| {
scores: BeatleaderScore[];
pageInfo: {
totalScores: number;
page: number;
totalPages: number;
};
}
| undefined
> {
if (limit > 100) {
throw new Error("Limit cannot be greater than 100");
}
const response = await fetchQueue.fetch(
formatString(PLAYER_SCORES_URL, true, playerId, page),
);
const json = await response.json();
// Check if there was an error fetching the user data
console.log(json);
const metadata = json.metadata;
return {
scores: json.data as BeatleaderScore[],
pageInfo: {
totalScores: json.totalScores,
page: json.page,
totalPages: Math.ceil(json.totalScores / metadata.itemsPerPage),
},
};
}
/**
* Gets all of the players for the given player id
*
* @param playerId the id of the player
* @param searchType the type of search to perform
* @param callback a callback to call when a page is fetched
* @returns a list of scores
*/
async function fetchAllScores(
playerId: string,
callback?: (currentPage: number, totalPages: number) => void,
): Promise<BeatleaderScore[] | undefined> {
const scores = new Array();
let done = false,
page = 1;
do {
const response = await fetchScores(playerId, page);
if (response == undefined) {
done = true;
break;
}
const { scores: scoresFetched } = response;
if (scoresFetched.length === 0) {
done = true;
break;
}
scores.push(...scoresFetched);
if (callback) {
callback(page, response.pageInfo.totalPages);
}
page++;
} while (!done);
return scores as BeatleaderScore[];
}
export const BeatLeaderAPI = {
fetchScores,
fetchAllScores,
};