highlights mapsets of favourite mappers
// ==UserScript==
// @name osu! mapper highlight
// @namespace https://osu.ppy.sh/users/10767001
// @version 2026-08-06
// @description highlights mapsets of favourite mappers
// @author Hagama
// @match https://osu.ppy.sh/*
// @match http://osu.ppy.sh/*
// @icon https://osu.ppy.sh/favicon.ico
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
console.log('Tampermonkey osu-mapper-highlight loaded');
const STORAGE_KEY = 'tm-mapper-list';
const id = 'tm-mapper-highlight';
if (document.getElementById(id)) return;
const defaultMappers = [1];
function loadMappers() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return [...defaultMappers];
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed)) return [...defaultMappers];
return parsed;
} catch {
return [...defaultMappers];
}
}
function saveMappers(arr) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(arr));
} catch {}
}
let userIds = loadMappers();
console.log(userIds);
function panelOverride(root = document) {
const aSel = userIds.map(id => `a[data-user-id="${CSS.escape(String(id))}"]`).join(',');
root.querySelectorAll('div.beatmapset-panel').forEach(div => {
div.style.outline = div.querySelector(aSel) ? '2px solid #2ecc71' : '';
});
}
function userpageLoaded() {
const container = document.querySelector(".profile-detail-bar");
if (!container) return;
const divs = container.querySelectorAll("div");
const secondDiv = divs[1];
if (!secondDiv) return;
if (container.querySelector(".tm-add-mapper-button")) return;
const newDiv = document.createElement("div");
newDiv.className = "tm-add-mapper-button";
newDiv.appendChild(mapperBtn);
secondDiv.insertAdjacentElement("afterend", newDiv);
}
const mapperBtn = document.createElement('button');
mapperBtn.type = 'button';
mapperBtn.classList.add("user-action-button");
mapperBtn.classList.add("user-action-button--profile-page");
mapperBtn.style.width = '100px';
mapperBtn.textContent = 'HIGHLIGHT';
function pageUrl() {
return window.location.href;
}
function onUrlChange() {
const url = pageUrl();
const urlOK = url.indexOf('ppy.sh/users/');
const urlStuff = url.split('ppy.sh/users/')[1] ?? '';
const slashPos = urlStuff.indexOf('/');
const mapperId = (slashPos === -1) ? urlStuff : urlStuff.slice(0, slashPos);
(userIds.includes(mapperId)) ? mapperBtn.classList.add('user-action-button--friend') : mapperBtn.classList.remove('user-action-button--friend');
const idx = userIds.indexOf(mapperId);
if (urlOK === -1) {
mapperBtn.disabled = true;
mapperBtn.style.cursor = 'not-allowed';
mapperBtn.style.background = 'rgba(0,0,0,0.4)';
mapperBtn.style.color = '#6A6A6A';
} else {
mapperBtn.disabled = false;
mapperBtn.style.cursor = 'pointer';
mapperBtn.style.background = '';
mapperBtn.style.color = '#fff';
}
}
mapperBtn.addEventListener('click', () => {
const url = pageUrl();
const urlStuff = url.split('ppy.sh/users/')[1] ?? '';
const slashPos = urlStuff.indexOf('/');
const mapperId = (slashPos === -1) ? urlStuff : urlStuff.slice(0, slashPos);
const idx = userIds.indexOf(mapperId);
if (idx !== -1) userIds.splice(idx, 1);
else userIds.push(mapperId);
saveMappers(userIds);
panelOverride();
onUrlChange();
});
if ("hidden" in document){
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === 'visible') {
userIds = loadMappers();
onUrlChange();
}
});
}
(function () {
const origPush = history.pushState;
history.pushState = function (...args) {
const ret = origPush.apply(this, args);
onUrlChange();
return ret;
};
const origReplace = history.replaceState;
history.replaceState = function (...args) {
const ret = origReplace.apply(this, args);
onUrlChange();
return ret;
};
window.addEventListener('popstate', onUrlChange);
window.addEventListener('hashchange', onUrlChange);
onUrlChange();
})();
const obs = new MutationObserver(() => {panelOverride();userpageLoaded();});
obs.observe(document.documentElement, { childList: true, subtree: true });
})();