re-code the userscript
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 7s
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 7s
This commit is contained in:
parent
a8158c73c8
commit
c8990f21cd
31
.gitea/workflows/ci.yml
Normal file
31
.gitea/workflows/ci.yml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
name: Deploy App
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["master"]
|
||||||
|
paths-ignore:
|
||||||
|
- .gitignore
|
||||||
|
- README.md
|
||||||
|
- LICENSE
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
arch: ["ubuntu-latest"]
|
||||||
|
runs-on: ${{ matrix.arch }}
|
||||||
|
|
||||||
|
# Steps to run
|
||||||
|
steps:
|
||||||
|
# Checkout the repo
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
# Deploy to Dokku
|
||||||
|
- name: Push to dokku
|
||||||
|
uses: dokku/github-action@master
|
||||||
|
with:
|
||||||
|
git_remote_url: "ssh://dokku@10.0.50.175:22/ssu-scripts"
|
||||||
|
ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}
|
210
.gitignore
vendored
Normal file
210
.gitignore
vendored
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
### JetBrains template
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# AWS User-specific
|
||||||
|
.idea/**/aws.xml
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# SonarLint plugin
|
||||||
|
.idea/sonarlint/
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### Node template
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
5
.idea/.gitignore
generated
vendored
Normal file
5
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
35
Dockerfile
Normal file
35
Dockerfile
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
FROM node:21-alpine AS base
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
|
||||||
|
RUN \
|
||||||
|
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
||||||
|
elif [ -f package-lock.json ]; then npm ci; \
|
||||||
|
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
|
||||||
|
else echo "Lockfile not found." && exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Rebuild the source code only when needed
|
||||||
|
FROM base AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Set the environment to production
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
# Build the scripts
|
||||||
|
RUN \
|
||||||
|
if [ -f yarn.lock ]; then yarn run build; \
|
||||||
|
elif [ -f package-lock.json ]; then npm run build; \
|
||||||
|
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
|
||||||
|
else echo "Lockfile not found." && exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
FROM nginx:alpine AS runner
|
||||||
|
|
||||||
|
COPY --from=builder /app/dist/ /usr/share/nginx/html
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
23
package.json
Normal file
23
package.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "scoresaberutils-script",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "The TamperMonkey script for ScoreSaber Utils",
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack --config webpack.config.js",
|
||||||
|
"dev": "webpack --config webpack.config.js --watch"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon-webpack-plugin": "^4.8.2",
|
||||||
|
"ts-loader": "^9.5.1",
|
||||||
|
"typescript": "^5.4.5",
|
||||||
|
"webpack": "^5.91.0",
|
||||||
|
"webpack-cli": "^5.1.4",
|
||||||
|
"webpack-userscript": "^3.2.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": "^4.17.21"
|
||||||
|
}
|
||||||
|
}
|
1350
pnpm-lock.yaml
generated
Normal file
1350
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,122 +0,0 @@
|
|||||||
// ==UserScript==
|
|
||||||
// @name ScoreSaber Utils
|
|
||||||
// @namespace https://ssu.fascinated.cc
|
|
||||||
// @version 1.0.7
|
|
||||||
// @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-Script/raw/branch/master/scoresaber-utils.user.js
|
|
||||||
// @downloadURL https://git.fascinated.cc/Fascinated/ScoreSaberUtils-Script/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<any>} The JSON response from the API
|
|
||||||
*/
|
|
||||||
async function fetchData(url) {
|
|
||||||
const response = await fetch(url);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to fetch data: ${response.status} ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = `
|
|
||||||
<span class="stat-title ${svelteClass}">${stat}</span>
|
|
||||||
<span class="stat-spacer ${svelteClass}"></span>
|
|
||||||
<span class="stat-content ${svelteClass} has-hover" title="${hoverText}">${value}</span>
|
|
||||||
`;
|
|
||||||
container.appendChild(statElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delays execution for the specified duration.
|
|
||||||
*
|
|
||||||
* @param {number} ms The duration to delay in milliseconds
|
|
||||||
* @returns {Promise<void>} 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) {
|
|
||||||
// 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(() => {
|
|
||||||
const currentUrl = location.pathname; // Get the current URL without parameters
|
|
||||||
if (currentUrl == previousUrl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
previousUrl = currentUrl;
|
|
||||||
console.log("Switching Page:", currentUrl);
|
|
||||||
|
|
||||||
// Load player data on player pages
|
|
||||||
if (currentUrl.startsWith("/u/")) {
|
|
||||||
loadPlayerData(currentUrl);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const config = { subtree: true, childList: true };
|
|
||||||
observer.observe(document, config);
|
|
74
src/common/page-utils.ts
Normal file
74
src/common/page-utils.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* Get a callback for when the page changes
|
||||||
|
*
|
||||||
|
* @param callback The callback to call when the page changes
|
||||||
|
*/
|
||||||
|
export function pageChangeCallback(callback: (path: string) => void) {
|
||||||
|
let previousUrl = "";
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
const currentUrl = location.pathname; // Get the current URL without parameters
|
||||||
|
if (currentUrl == previousUrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
previousUrl = currentUrl;
|
||||||
|
|
||||||
|
callback(currentUrl);
|
||||||
|
});
|
||||||
|
const config = { subtree: true, childList: true };
|
||||||
|
observer.observe(document, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the Svelte class of an element
|
||||||
|
*
|
||||||
|
* @param baseClass The base class of the element
|
||||||
|
*/
|
||||||
|
export function getSvelteClass(baseClass: string): string | null {
|
||||||
|
const element = document.querySelector(baseClass);
|
||||||
|
if (!element) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the Svelte class
|
||||||
|
for (let string of element.className.split(" ")) {
|
||||||
|
if (string.startsWith("svelte-")) {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an element from the page and waits
|
||||||
|
* for it to load or be available
|
||||||
|
*
|
||||||
|
* @param selector the selector of the element
|
||||||
|
* @param checkInterval the interval to check for the element
|
||||||
|
*/
|
||||||
|
export function getElement(selector: string, checkInterval: number = 250): Promise<HTMLElement> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const element = document.querySelector(selector);
|
||||||
|
if (element) {
|
||||||
|
resolve(element as HTMLElement);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let checkCount = 0;
|
||||||
|
const checkElement = () => {
|
||||||
|
const element = document.querySelector(selector);
|
||||||
|
if (element) {
|
||||||
|
clearInterval(interval);
|
||||||
|
resolve(element as HTMLElement);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
checkCount++;
|
||||||
|
if (checkCount * checkInterval >= 2500) { // Give up after 2.5 seconds
|
||||||
|
clearInterval(interval);
|
||||||
|
reject(new Error("Element not found within timeout"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const interval = setInterval(checkElement, checkInterval);
|
||||||
|
});
|
||||||
|
}
|
26
src/common/player.ts
Normal file
26
src/common/player.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import {API_URL} from "../consts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a player from the ScoreSaber Utils API
|
||||||
|
*
|
||||||
|
* @param id The player's ID
|
||||||
|
*/
|
||||||
|
export async function getPlayer(id: string) {
|
||||||
|
const response = await fetch(`${API_URL}/account/${id}`);
|
||||||
|
|
||||||
|
// There was an error fetching the player
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to fetch player");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the player's data
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the player id from the current URL.
|
||||||
|
*/
|
||||||
|
export function getPlayerIdFromUrl(): string {
|
||||||
|
const url = new URL(location.href);
|
||||||
|
return url.pathname.split("/")[2];
|
||||||
|
}
|
9
src/common/utils.ts
Normal file
9
src/common/utils.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Delays execution for the specified duration.
|
||||||
|
*
|
||||||
|
* @param ms The duration to delay in milliseconds
|
||||||
|
* @returns A promise that resolves after the delay
|
||||||
|
*/
|
||||||
|
export function sleep(ms: number): Promise<void> {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
4
src/consts.ts
Normal file
4
src/consts.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* The URL of the ScoreSaber Utils API
|
||||||
|
*/
|
||||||
|
export const API_URL = "https://ssu.fascinated.cc";
|
3
src/index.ts
Normal file
3
src/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import PageHandler from "./pages/page-handler";
|
||||||
|
|
||||||
|
new PageHandler();
|
57
src/pages/impl/player-page.ts
Normal file
57
src/pages/impl/player-page.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import Page from "../page";
|
||||||
|
import {getElement, getSvelteClass} from "../../common/page-utils";
|
||||||
|
import {getPlayer, getPlayerIdFromUrl} from "../../common/player";
|
||||||
|
|
||||||
|
export default class PlayerPage extends Page {
|
||||||
|
constructor() {
|
||||||
|
super("/u/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onLoad() {
|
||||||
|
try {
|
||||||
|
// Wait for the title element to load, so we know the page is fully loaded
|
||||||
|
const titleElement = await getElement(".title.is-5.player.has-text-centered-mobile", 250);
|
||||||
|
|
||||||
|
const id = getPlayerIdFromUrl();
|
||||||
|
const player = await getPlayer(id);
|
||||||
|
|
||||||
|
console.log(player);
|
||||||
|
await this.addStats(player);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to load player page", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the custom stats to the player's page
|
||||||
|
*
|
||||||
|
* @param player the player to add stats for
|
||||||
|
*/
|
||||||
|
private async addStats(player: any) {
|
||||||
|
await this.addStat(
|
||||||
|
"+1 PP",
|
||||||
|
`${player.rawPerGlobalPerformancePoints.toFixed(2)}pp`,
|
||||||
|
"Raw performance points to gain +1 global PP"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a stat to the player's stats
|
||||||
|
*
|
||||||
|
* @param stat the title of the stat
|
||||||
|
* @param value the value of the stat
|
||||||
|
* @param hover the hover text of the stat
|
||||||
|
*/
|
||||||
|
private async addStat(stat: string, value: string, hover?: string) {
|
||||||
|
const statsContainer = await getElement(".stats-container");
|
||||||
|
const statElement = document.createElement("div");
|
||||||
|
const svelteClass = getSvelteClass(".stats-container");
|
||||||
|
|
||||||
|
statElement.className = `stat-item ${svelteClass}`;
|
||||||
|
statElement.innerHTML = `
|
||||||
|
<span class="stat-title ${svelteClass}">${stat}</span>
|
||||||
|
<span class="stat-spacer ${svelteClass}"></span>
|
||||||
|
<span class="stat-content ${hover && "has-hover"} ${svelteClass}" ${hover && `title="${hover}"`}>${value}</span>`;
|
||||||
|
statsContainer.appendChild(statElement);
|
||||||
|
}
|
||||||
|
}
|
34
src/pages/page-handler.ts
Normal file
34
src/pages/page-handler.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import Page from "./page";
|
||||||
|
import PlayerPage from "./impl/player-page";
|
||||||
|
import {pageChangeCallback} from "../common/page-utils";
|
||||||
|
|
||||||
|
export default class PageHandler {
|
||||||
|
private pages: Page[] = [];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// Register the pages to handle
|
||||||
|
this.registerPage(new PlayerPage());
|
||||||
|
|
||||||
|
// Handle page changes
|
||||||
|
pageChangeCallback((path) => {
|
||||||
|
console.log(`Page changed to: ${path}`);
|
||||||
|
|
||||||
|
for (let page of this.pages) {
|
||||||
|
if (path.startsWith(page.route)) {
|
||||||
|
console.log(`Handling page: ${page.route}`);
|
||||||
|
page.onLoad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a page to be handled
|
||||||
|
*
|
||||||
|
* @param page The page to register
|
||||||
|
*/
|
||||||
|
private registerPage(page: Page) {
|
||||||
|
console.log(`Registered page: ${page.route}`)
|
||||||
|
this.pages.push(page);
|
||||||
|
}
|
||||||
|
}
|
24
src/pages/page.ts
Normal file
24
src/pages/page.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
export default class Page {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The route of the page
|
||||||
|
* eg: /ranking
|
||||||
|
*/
|
||||||
|
private readonly _route: string;
|
||||||
|
|
||||||
|
constructor(route: string) {
|
||||||
|
this._route = route;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This gets called when the page is loaded
|
||||||
|
*/
|
||||||
|
public onLoad() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The route of the page
|
||||||
|
*/
|
||||||
|
get route(): string {
|
||||||
|
return this._route;
|
||||||
|
}
|
||||||
|
}
|
11
tsconfig.json
Normal file
11
tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist/",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"module": "es6",
|
||||||
|
"target": "es5",
|
||||||
|
"jsx": "react",
|
||||||
|
"allowJs": true,
|
||||||
|
"moduleResolution": "node"
|
||||||
|
}
|
||||||
|
}
|
32
webpack.config.js
Normal file
32
webpack.config.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const { UserscriptPlugin } = require('webpack-userscript');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: './src/index.ts',
|
||||||
|
mode: process.env.NODE_ENV || 'development',
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
use: 'ts-loader',
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new UserscriptPlugin({
|
||||||
|
headers: {
|
||||||
|
name: "ScoreSaber Utils",
|
||||||
|
"run-at": "document-end",
|
||||||
|
match: "https://scoresaber.com/*"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
output: {
|
||||||
|
filename: 'bundle.js',
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
},
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user