commit 5ba33056b69f9b4f9673830027bcd290bcdbaa7b Author: Lee Date: Thu Apr 25 19:12:46 2024 +0000 Add scoresaber-utils.user.js diff --git a/scoresaber-utils.user.js b/scoresaber-utils.user.js new file mode 100644 index 0000000..288b52d --- /dev/null +++ b/scoresaber-utils.user.js @@ -0,0 +1,123 @@ +// ==UserScript== +// @name ScoreSaber Utils +// @namespace https://ssu.fascinated.cc +// @version 1.0.5 +// @description Useful additions to ScoreSaber! +// @author Fascinated +// @match https://scoresaber.com/* +// @icon https://www.google.com/s2/favicons?sz=64&domain=scoresaber.com +// @license MIT +// @updateURL https://git.fascinated.cc/Fascinated/ScoreSaberUtils-Backend/raw/branch/master/scoresaber-utils.user.js +// @downloadURL https://git.fascinated.cc/Fascinated/ScoreSaberUtils-Backend/raw/branch/master/scoresaber-utils.user.js +// @run-at document-end +// ==/UserScript== + +/** + * Fetches data from an API endpoint. + * + * @param {string} url The URL of the API endpoint + * @returns {Promise} The JSON response from the API + */ +async function fetchData(url) { + const response = await fetch(url); + return await response.json(); +} + +/** + * 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(containerSelector, stat, value, hoverText) { + const container = document.querySelector(containerSelector); + if (!container) return; + + const svelteClass = container.classList.item(1); + + const statElement = document.createElement("div"); + statElement.className = `stat-item ${svelteClass}`; + statElement.innerHTML = ` + ${stat} + + ${value} + `; + container.appendChild(statElement); +} + +/** + * Delays execution for the specified duration. + * + * @param {number} ms The duration to delay in milliseconds + * @returns {Promise} A promise that resolves after the delay + */ +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +/** + * Loads ScoreSaber Utils data on player pages. + */ +async function loadPlayerData(path) { + if (!path) { + path = window.location.pathname; + } + path = path.replace("https://scoresaber.com", ""); + + const isPlayerPage = path.startsWith("/u/"); + + if (!isPlayerPage) { + // Only run on player pages + return; + } + + // Wait for the stats container to load + while (!document.querySelector(".stats-container")) { + await sleep(250); + } + const playerId = path.split("/")[2]; + + // Get the title element + await sleep(250); + const titleElement = document.querySelector(".title.is-5.player.has-text-centered-mobile"); + if (!titleElement) { + console.error("Failed to find title element"); + return; + } + const svelteClass = titleElement.classList.item(1); + + // 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(); +} + +// Watch for URL changes +let previousUrl = ""; +const observer = new MutationObserver(function (mutations) { + const currentUrl = location.pathname; // Get the current URL without parameters + if (currentUrl !== previousUrl) { + previousUrl = currentUrl; + + loadPlayerData(currentUrl); + } +}); +const config = { subtree: true, childList: true }; +observer.observe(document, config);