diff --git a/scoresaber-utils.user.js b/scoresaber-utils.user.js
index 8aaed9b..7dc2f5d 100644
--- a/scoresaber-utils.user.js
+++ b/scoresaber-utils.user.js
@@ -12,76 +12,96 @@
// @run-at document-end
// ==/UserScript==
-let loaded = false;
-
/**
- * Fetches the player from ScoreSaber Utils.
+ * Fetches data from an API endpoint.
*
- * @param {string} id the player's ID
- * @returns the player's data
+ * @param {string} url The URL of the API endpoint
+ * @returns The JSON response from the API
*/
-async function fetchPlayer(id) {
- const response = await fetch(`https://ssu.fascinated.cc/account/${id}`);
+async function fetchData(url) {
+ const response = await fetch(url);
return await response.json();
}
/**
- * Handles the player page logic.
- */
-async function playerPage() {
- console.log("Player page loaded!");
- const id = window.location.pathname.split("/")[2];
- const player = await fetchPlayer(id);
-
- addStat(
- "+1 PP",
- player.rawPerGlobalPerformancePoints.toFixed(2) + "pp",
- "The amount of pp to increase the global pp by 1pp"
- );
-}
-
-/**
- * Inserts a stat into the player's stats.
+ * Inserts a stat into the specified container.
*
+ * @param {string} containerSelector The selector for the container to insert the stat into
* @param {string} stat The stat name
* @param {string} value The stat value
* @param {string} hoverText The hover text
*/
-function addStat(stat, value, hoverText) {
- const statsElement = document.getElementsByClassName("stats-container")[0];
- const svelteClass = statsElement.getAttribute("class").split(" ")[1];
+function addStat(containerSelector, stat, value, hoverText) {
+ const container = document.querySelector(containerSelector);
+ if (!container) return;
+
+ const svelteClass = container.getAttribute("class").split(" ")[1];
const statElement = document.createElement("div");
statElement.className = `stat-item ${svelteClass}`;
statElement.innerHTML = `
- ${stat}
-
- ${value}
+ ${stat}
+
+ ${value}
`;
- statsElement.appendChild(statElement);
+ container.appendChild(statElement);
}
+/**
+ * Delays execution for the specified duration.
+ *
+ * @param {number} ms The duration to delay in milliseconds
+ * @returns A promise that resolves after the delay
+ */
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
-async function load() {
- "use strict";
- if (loaded) return;
-
+/**
+ * Loads ScoreSaber Utils data on player pages.
+ */
+async function loadPlayerData() {
const path = window.location.pathname;
const isPlayerPage = path.startsWith("/u/");
- if (isPlayerPage) {
- // Wait for the stats container to load
- while (document.getElementsByClassName("stats-container").length === 0) {
- await sleep(250);
- }
-
- playerPage();
+ if (!isPlayerPage) {
+ // Only run on player pages
+ return;
}
- loaded = true;
+ // Wait for the stats container to load
+ while (!document.querySelector(".stats-container")) {
+ await sleep(250);
+ }
+ const playerId = path.split("/")[2];
+
+ // Get the title element
+ const titleElement = document.querySelector(".title.is-5.player.has-text-centered-mobile");
+ if (!titleElement) return;
+ const svelteClass = titleElement
+ .getAttribute("class")
+ .split(" ")
+ .find((c) => c.startsWith("svelte"));
+
+ // Add a loading indicator
+ const loadingElement = document.createElement("span");
+ loadingElement.className = `title-header pp ${svelteClass}`;
+ loadingElement.textContent = "Loading ScoreSaber Utils Data...";
+ titleElement.appendChild(loadingElement);
+
+ try {
+ const playerData = await fetchData(`https://ssu.fascinated.cc/account/${playerId}`);
+ addStat(
+ ".stats-container",
+ "+1 PP",
+ `${playerData.rawPerGlobalPerformancePoints.toFixed(2)}pp`,
+ "The amount of pp to increase the global pp by 1pp"
+ );
+ } catch (error) {
+ console.error("Failed to load player data:", error);
+ }
+ // Remove the loading indicator
+ loadingElement.remove();
}
-load();
+loadPlayerData();
diff --git a/src/main/java/cc/fascinated/backend/model/account/Account.java b/src/main/java/cc/fascinated/backend/model/account/Account.java
index 2af43b5..84b51a0 100644
--- a/src/main/java/cc/fascinated/backend/model/account/Account.java
+++ b/src/main/java/cc/fascinated/backend/model/account/Account.java
@@ -163,6 +163,9 @@ public class Account {
* @return The bio.
*/
public static Bio fromRaw(String raw) {
+ if (raw == null || raw.isEmpty()) {
+ return null;
+ }
return new Bio(
raw.split("\n"),
raw.replaceAll("<[^>]*>", "").split("\n")