Created
January 7, 2022 11:34
-
-
Save mzhang28/db2a3c123711ff2a49a5e3acbdc49621 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name OsuTweaks | |
// @version 0.1.0 | |
// @author IOException | |
// @run-at document-start | |
// @include http://osu.ppy.sh* | |
// @include https://osu.ppy.sh* | |
// @require https://code.jquery.com/jquery-3.6.0.min.js | |
// ==/UserScript== | |
// UTILS ====================================================================== | |
// HSL / RGB conversion functions | |
// minified, original is https://gist.github.com/mjackson/5311256 | |
function rgbToHsl(r,a,e){r/=255,a/=255,e/=255;var n,t=Math.max(r,a,e),c=Math.min(r,a,e),s=(t+c)/2;if(t==c)n=u=0;else{var i=t-c,u=.5<s?i/(2-t-c):i/(t+c);switch(t){case r:n=(a-e)/i+(a<e?6:0);break;case a:n=(e-r)/i+2;break;case e:n=(r-a)/i+4}n/=6}return[n,u,s]}function hslToRgb(r,a,e){var n,t,c;function s(r,a,e){return e<0&&(e+=1),1<e&&--e,e<1/6?r+6*(a-r)*e:e<.5?a:e<2/3?r+(a-r)*(2/3-e)*6:r}return 0==a?n=t=c=e:(n=s(a=2*e-(e=e<.5?e*(1+a):e+a-e*a),e,r+1/3),t=s(a,e,r),c=s(a,e,r-1/3)),[255*n,255*t,255*c]} | |
// Color parsing function | |
// minified, original is from https://stackoverflow.com/a/68580275 | |
function parseCssColor(e){const o=document.createElement("div");document.body.appendChild(o),o.style.color=e;e=getComputedStyle(o).color.match(/[\.\d]+/g).map(Number);return o.remove(),e} | |
// START ====================================================================== | |
console.log("OsuTweaks"); | |
let currentUrl = null; | |
let listenersToPop = []; | |
function wait(func, pred) { | |
return new Promise((resolve) => { | |
var interval; | |
let test = function() { | |
let res = func(); | |
if (b = pred(res)) { | |
if (interval) clearInterval(interval); | |
resolve(res); | |
} | |
return b; | |
}; | |
if (!test()) { | |
interval = setInterval(test, 1000); | |
} | |
}); | |
} | |
function waitForElementToExist(selector) { | |
return new Promise((resolve) => { | |
var interval; | |
let findEl = function() { | |
let el = document.querySelector(selector); | |
console.log(`Finding ${selector}`, el); | |
if (el !== null) { | |
console.log("Found.", el); | |
if (interval) clearInterval(interval); | |
resolve(el); | |
} | |
return el != null; | |
}; | |
if (!findEl()) { | |
interval = setInterval(findEl, 1000); | |
} | |
}); | |
} | |
let pathHandlers = [ | |
[/\/users\/(\d+)(\/(osu|taiko|fruits|mania).+)?/, async (m) => { | |
console.log("User page"); | |
let currentUser = await wait(() => unsafeWindow.currentUser, c => !!c); | |
console.log(currentUser); | |
}], | |
[/\/beatmapsets\/(\d+).?/, async (m) => { | |
console.log("Beatmap page"); | |
// get the beatmapset data | |
// TODO: error checking? | |
let beatmapsetData = JSON.parse($("#json-beatmapset").text()); | |
// console.log(beatmapsetData); | |
// remove existing difficulty name indicator | |
let diffNameIndicator = await waitForElementToExist(".beatmapset-header__diff-name"); | |
diffNameIndicator.parentNode.removeChild(diffNameIndicator); | |
let starRatingIndicator = await waitForElementToExist(".beatmapset-header__star-difficulty"); | |
starRatingIndicator.parentNode.removeChild(starRatingIndicator); | |
// put the names into the bubbles | |
let beatmapMap = new Map(); | |
for (let beatmap of beatmapsetData.beatmaps) { | |
beatmapMap.set(beatmap.id, beatmap); | |
} | |
let beatmapPicker = await waitForElementToExist(".beatmapset-beatmap-picker"); | |
for (let child of beatmapPicker.children) { | |
if (!child.href) continue; | |
let targetBeatmapId = parseInt(child.href.split("/").at(-1)); | |
let correspondingBeatmap = beatmapMap.get(targetBeatmapId); | |
// create the elements and add it to screen | |
let icon = child.children[0]; | |
let container = document.createElement("div"); | |
container.style.display = "flex"; | |
container.style.flexDirection = "column"; | |
container.style.gap = "0"; | |
container.style.transform = "translateZ(1px)"; | |
let diffNameContainer = document.createElement("div"); | |
let diffNameNode = document.createTextNode(correspondingBeatmap.version); | |
diffNameContainer.appendChild(diffNameNode); | |
let colorString = icon.style.getPropertyValue("--diff"); | |
let color = parseCssColor(colorString); | |
let hsl = rgbToHsl(...color); | |
// make sure the colors aren't too dim | |
if (hsl[2] < .5) hsl[2] = .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); | |
let starRating = correspondingBeatmap.difficulty_rating.toLocaleString("en-US", { | |
minimumFractionDigits: 2, | |
maximumFractionDigits: 2, | |
useGrouping: false | |
}); | |
let infoContainer = document.createElement("small"); | |
let 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); | |
container.appendChild(infoContainer); | |
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"; | |
child.appendChild(container); | |
} | |
}], | |
]; | |
// this function is run whenever a new "page" is visited | |
// unfortunately the osu website is basically an SPA, so | |
// clicking links doesn't actually trigger a full refresh | |
let handleActualPageVisit = (path) => { | |
console.log(`New page visit to ${path}`); | |
let pathName = window.location.pathname; | |
for (let [regex, callback] of pathHandlers) { | |
let m = pathName.match(regex); | |
if (m !== null) { | |
// a match was found | |
callback(m); | |
break; | |
} | |
} | |
}; | |
// wrap in a function to prevent global namespace pollution | |
document.addEventListener("DOMContentLoaded", () => { | |
"use strict"; | |
// main initialization function | |
// detects which page we're on | |
$(document).ready(() => { | |
// detect page change, ripped from osuplus | |
// this is really ugly but whatever | |
// spent an hour trying to jack some addEventListener calls with no success | |
var currentBody = null; | |
setInterval(function() { | |
if (currentBody !== null && currentBody !== document.body) { | |
currentBody = document.body; | |
handleActualPageVisit(window.location.pathname); | |
} else if (currentBody === null) { | |
currentBody = document.body; | |
} | |
}, 1000); | |
// the very first time the browser is opened, handle the page visit | |
handleActualPageVisit(window.location.pathname); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment