From cf9cf06d2b6aab1dee46fd7d36720ccd31f0a54f Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Sat, 27 Jul 2024 16:28:33 -0500 Subject: [PATCH] initial --- .gitignore | 175 ++++++++++++++++++++++++++++++++++++++++++ .vscode/settings.json | 8 ++ README.md | 15 ++++ biome.json | 17 ++++ bun.lockb | Bin 0 -> 116474 bytes index.ts | 1 + manifest.json | 11 +++ package.json | 23 ++++++ src/colors.ts | 170 ++++++++++++++++++++++++++++++++++++++++ src/dom.ts | 63 +++++++++++++++ src/pages/beatmap.ts | 104 +++++++++++++++++++++++++ src/pages/user.ts | 8 ++ src/script.ts | 27 +++++++ src/utils.ts | 12 +++ tsconfig.json | 27 +++++++ vite.config.ts | 9 +++ 16 files changed, 670 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 README.md create mode 100644 biome.json create mode 100644 bun.lockb create mode 100644 index.ts create mode 100644 manifest.json create mode 100644 package.json create mode 100644 src/colors.ts create mode 100644 src/dom.ts create mode 100644 src/pages/beatmap.ts create mode 100644 src/pages/user.ts create mode 100644 src/script.ts create mode 100644 src/utils.ts create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b1ee42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# 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/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# 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.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ba88f51 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "json.schemas": [ + { + "fileMatch": ["manifest.json"], + "url": "https://json.schemastore.org/chrome-manifest" + } + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..1cc81ac --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# tweaks + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.1.20. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..5ac9bb4 --- /dev/null +++ b/biome.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", + "organizeImports": { + "enabled": true + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + } +} diff --git a/bun.lockb b/bun.lockb new file mode 100644 index 0000000000000000000000000000000000000000..4d78c4ed6ade0d287eb83660bc314e6e3161c3e4 GIT binary patch literal 116474 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 DG5q9~ literal 0 HcmV?d00001 diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..f67b2c6 --- /dev/null +++ b/index.ts @@ -0,0 +1 @@ +console.log("Hello via Bun!"); \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..573bb57 --- /dev/null +++ b/manifest.json @@ -0,0 +1,11 @@ +{ + "name": "OsuTweaks", + "version": "0.1.0", + "manifest_version": 2, + "content_scripts": [ + { + "matches": ["*://osu.ppy.sh/*"], + "js": ["src/script.ts"] + } + ] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1b6a595 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "tweaks", + "module": "index.ts", + "type": "module", + "scripts": { + "dev": "vite" + }, + "devDependencies": { + "@biomejs/biome": "^1.8.3", + "@types/bun": "latest", + "@types/lodash.isequal": "^4.5.8", + "@types/webextension-polyfill": "^0.10.7", + "vite-plugin-web-extension": "^4.1.6" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "lodash.isequal": "^4.5.0", + "vite": "^5.3.5", + "webextension-polyfill": "^0.12.0" + } +} \ No newline at end of file diff --git a/src/colors.ts b/src/colors.ts new file mode 100644 index 0000000..2486162 --- /dev/null +++ b/src/colors.ts @@ -0,0 +1,170 @@ +// From https://gist.githubusercontent.com/mjackson/5311256/raw/132d6d1f39bf422e03c8ab86b5329d6f4dfbc383/color-conversion-algorithms.js + +/** + * Converts an RGB color value to HSL. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * Assumes r, g, and b are contained in the set [0, 255] and + * returns h, s, and l in the set [0, 1]. + * + * @param Number r The red color value + * @param Number g The green color value + * @param Number b The blue color value + * @return Array The HSL representation + */ +export function rgbToHsl(r: number, g: number, b: number) { + (r /= 255), (g /= 255), (b /= 255); + + var max = Math.max(r, g, b), + min = Math.min(r, g, b); + var h, + s, + l = (max + min) / 2; + + if (max == min) { + h = s = 0; // achromatic + } else { + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; + } + + h /= 6; + } + + return [h, s, l]; +} + +/** + * Converts an HSL color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * Assumes h, s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * + * @param Number h The hue + * @param Number s The saturation + * @param Number l The lightness + * @return Array The RGB representation + */ +export function hslToRgb(h, s, l) { + var r, g, b; + + if (s == 0) { + r = g = b = l; // achromatic + } else { + function hue2rgb(p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + } + + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + + return [r * 255, g * 255, b * 255]; +} + +/** + * Converts an RGB color value to HSV. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSV_color_space. + * Assumes r, g, and b are contained in the set [0, 255] and + * returns h, s, and v in the set [0, 1]. + * + * @param Number r The red color value + * @param Number g The green color value + * @param Number b The blue color value + * @return Array The HSV representation + */ +export function rgbToHsv(r, g, b) { + (r /= 255), (g /= 255), (b /= 255); + + var max = Math.max(r, g, b), + min = Math.min(r, g, b); + var h, + s, + v = max; + + var d = max - min; + s = max == 0 ? 0 : d / max; + + if (max == min) { + h = 0; // achromatic + } else { + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; + } + + h /= 6; + } + + return [h, s, v]; +} + +/** + * Converts an HSV color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSV_color_space. + * Assumes h, s, and v are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * + * @param Number h The hue + * @param Number s The saturation + * @param Number v The value + * @return Array The RGB representation + */ +export function hsvToRgb(h, s, v) { + var r, g, b; + + var i = Math.floor(h * 6); + var f = h * 6 - i; + var p = v * (1 - s); + var q = v * (1 - f * s); + var t = v * (1 - (1 - f) * s); + + switch (i % 6) { + case 0: + (r = v), (g = t), (b = p); + break; + case 1: + (r = q), (g = v), (b = p); + break; + case 2: + (r = p), (g = v), (b = t); + break; + case 3: + (r = p), (g = q), (b = v); + break; + case 4: + (r = t), (g = p), (b = v); + break; + case 5: + (r = v), (g = p), (b = q); + break; + } + + return [r * 255, g * 255, b * 255]; +} diff --git a/src/dom.ts b/src/dom.ts new file mode 100644 index 0000000..4d73adb --- /dev/null +++ b/src/dom.ts @@ -0,0 +1,63 @@ +import isEqual from "lodash.isequal"; +const REFRESH_RATE = 1000; + +export async function waitForElementToExist( + selector: string, +): Promise { + let interval: Timer; + return new Promise((resolve) => { + interval = setInterval(() => { + const thing = document.querySelector(selector); + if (thing) { + clearInterval(interval); + resolve(thing); + } + }, REFRESH_RATE); + }); +} + +function onDocumentReady(func: () => void) { + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", func); + } else { + func(); + } +} + +export interface OsuPath { + pathname: string; + hash: string; +} + +export function addPageChangeHandler(func: (path: OsuPath) => void) { + onDocumentReady(() => { + console.log("Page loaded."); + + // detects which page we're on + // detect page change, ripped from osuplus + // this is really ugly but whatever + // spent an hour trying to jack some addEventListener calls with no success + + let currentPath: OsuPath = { + pathname: location.pathname, + hash: location.hash, + }; + + setInterval(() => { + const newPath = { + pathname: location.pathname, + hash: location.hash, + }; + + if (!isEqual(currentPath, newPath)) { + func(newPath); + currentPath = newPath; + } + }, REFRESH_RATE); + + // the very first time the browser is opened, handle the page visit + func(currentPath); + }); + + console.log("Isntallled event listener"); +} diff --git a/src/pages/beatmap.ts b/src/pages/beatmap.ts new file mode 100644 index 0000000..ccb0454 --- /dev/null +++ b/src/pages/beatmap.ts @@ -0,0 +1,104 @@ +import { hslToRgb, rgbToHsl } from "../colors"; +import { waitForElementToExist, type OsuPath } from "../dom"; +import { parseCssColor } from "../utils"; + +const MODE_HANDLER = /(?osu|taiko|fruits|mania)\/(?\d+)/; + +export async function beatmapPageHandler(m: RegExpMatchArray, path: OsuPath) { + const m2 = (path.hash ?? "").match(MODE_HANDLER); + const modeString = m2[1]; + + // get the beatmapset data + // TODO: error checking? + const dataEl = document.getElementById("json-beatmapset"); + const beatmapsetData = JSON.parse(dataEl?.textContent ?? "{}"); + + // remove existing difficulty name indicator + waitForElementToExist(".beatmapset-header__diff-name").then( + (diffNameIndicator) => { + diffNameIndicator.parentNode?.removeChild(diffNameIndicator); + }, + ); + + // const starRatingIndicator = await waitForElementToExist( + // ".beatmapset-header__star-difficulty", + // ); + + // starRatingIndicator.parentNode.removeChild(starRatingIndicator); + + // put the names into the bubbles + const beatmapMap = new Map(); + for (const beatmap of [ + ...beatmapsetData.beatmaps, + ...beatmapsetData.converts, + ]) + beatmapMap.set(JSON.stringify([beatmap.id, beatmap.mode]), beatmap); + + const beatmapPicker = await waitForElementToExist( + ".beatmapset-beatmap-picker", + ); + const beatmapPickerChildren: HTMLAnchorElement[] = Array.from( + beatmapPicker.children, + ); + for (const child of beatmapPickerChildren) { + if (!child.href) continue; + + const targetBeatmapId = Number.parseInt(child.href.split("/").at(-1)); + const correspondingBeatmap = beatmapMap.get( + JSON.stringify([targetBeatmapId, modeString]), + ); + + // create the elements and add it to screen + const icon = child.children[0]; + + const container = document.createElement("div"); + container.style.display = "flex"; + container.style.flexDirection = "column"; + container.style.gap = "0"; + container.style.transform = "translateZ(1px)"; + + const diffNameContainer = document.createElement("div"); + const diffNameNode = document.createTextNode(correspondingBeatmap.version); + diffNameContainer.appendChild(diffNameNode); + const colorString = icon.style.getPropertyValue("--diff"); + const color = parseCssColor(colorString); + const hsl = rgbToHsl(...color); + // make sure the colors aren't too dim + if (hsl[2] < 0.5) hsl[2] = 0.5; + hsl[2] *= 1.5; + if (hsl[2] > 1) hsl[2] = 1; + let [r, g, b] = hslToRgb(...hsl); + diffNameContainer.style.lineHeight = "0.75"; + child.style.color = `rgb(${r}, ${g}, ${b})`; + diffNameContainer.style.textShadow = "0 1px 3px rgba(0,0,0,.75)"; + container.appendChild(diffNameContainer); + + const starRating = correspondingBeatmap.difficulty_rating.toLocaleString( + "en-US", + { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + useGrouping: false, + }, + ); + const infoContainer = document.createElement("small"); + const infoNode = document.createTextNode(`${starRating}★`); + infoContainer.style.color = `rgb(${r}, ${g}, ${b})`; + infoContainer.style.textShadow = "0 1px 3px rgba(0,0,0,.75)"; + infoContainer.style.display = "block"; + infoContainer.style.fontSize = "75%"; + infoContainer.appendChild(infoNode); + + child.style.opacity = "1"; + child.style.width = "auto"; + child.style.height = "auto"; + child.style.display = "flex"; + child.style.alignItems = "center"; + child.style.paddingLeft = "8px"; + child.style.paddingRight = "12px"; + child.style.gap = "6px"; + container.appendChild(infoContainer); + + child.replaceChildren(icon, container); + } +} diff --git a/src/pages/user.ts b/src/pages/user.ts new file mode 100644 index 0000000..c8cb014 --- /dev/null +++ b/src/pages/user.ts @@ -0,0 +1,8 @@ +export async function userPageHandler(m, path) { + console.log("User page"); + let currentUser = await wait( + () => unsafeWindow.currentUser, + (c) => !!c, + ); + console.log(currentUser); +} diff --git a/src/script.ts b/src/script.ts new file mode 100644 index 0000000..2efb747 --- /dev/null +++ b/src/script.ts @@ -0,0 +1,27 @@ +import { addPageChangeHandler, type OsuPath } from "./dom"; +import { beatmapPageHandler } from "./pages/beatmap"; +import { userPageHandler } from "./pages/user"; + +console.log("OsuTweaks Extension"); + +const pathHandlers: [ + RegExp, + (m: RegExpMatchArray, _: OsuPath) => Promise, +][] = [ + [/\/users\/(\d+)(?\/(osu|taiko|fruits|mania).+)?/, userPageHandler], + [/\/beatmapsets\/(?\d+).?/, beatmapPageHandler], +]; + +addPageChangeHandler((path) => { + console.log("Changed to page", path); + + const { pathname } = path; + for (const [regex, callback] of pathHandlers) { + const m = pathname.match(regex); + if (m !== null) { + // a match was found + callback(m, path); + break; + } + } +}); diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..29aa586 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,12 @@ +// Color parsing function +// minified, original is from https://stackoverflow.com/a/68580275 +export function parseCssColor(str: string): [number, number, number] { + const div = document.createElement("div"); + document.body.appendChild(div); + div.style.color = str; + const res = getComputedStyle(div) + .color.match(/[\.\d]+/g) + .map(Number); + div.remove(); + return res; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..238655f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..c2f05fa --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vite"; +import webExtension from "vite-plugin-web-extension"; + +export default defineConfig({ + plugins: [webExtension({ + disableAutoLaunch: true, + browser: process.env.TARGET ?? "firefox" + })], +}); \ No newline at end of file