返回首頁 

osu! Beatmap Downloaded Indicator

Dim the beatmaps that are already downloaded in the osu! beatmap listing.


Install this script?
// ==UserScript==// @name               osu! Beatmap Downloaded Indicator// @name:zh            osu! Beatmap Downloaded Indicator// @name:zh-CN         osu! Beatmap Downloaded Indicator// @name:zh-TW         osu! Beatmap Downloaded Indicator// @namespace          https://github.com/karin0/osu-bdi// @version            0.4// @description        Dim the beatmaps that are already downloaded in the osu! beatmap listing.// @description:zh-cn  在 osu! 谱面列表页面中,暗化显示本地已下载的谱面。// @description:zh-tw  在 osu! 圖譜列表頁面中,暗化顯示本地已下載的圖譜。// @license            MIT// @author             karin0// @icon               https://osu.ppy.sh/favicon.ico// @match              http*://osu.ppy.sh/*// @grant              none// ==/UserScript==(function () {const port_default = '35677', obdi_page = 'https://github.com/karin0/osu-bdi';const css = document.createElement('style');css.type = 'text/css';css.innerText = `.di-done {filter: brightness(80%) contrast(80%) opacity(20%);}.di-input {width: 6em;height: 2.2em;margin: auto;padding: 10px;background-color: hsl(var(--hsl-b2));border: 1px solid hsl(var(--hsl-b4));-moz-appearance: textfield;}.di-input::-webkit-inner-spin-button {-webkit-appearance: none;margin: 0;}.di-status {align-items: center;display: flex;color: #fff;margin: auto 0.6em;}`;const map = new Map();const set = new Set();function get_id(e) {if (!e.dataset.diid) {const a = e.getElementsByTagName('a')[0];if (!a)return undefined;const href = a.getAttribute('href');if (!href)return undefined;const id = Number(href.substring(href.lastIndexOf('/') + 1));if (!id)return undefined;map[e.dataset.diid = id] = e;return id;}return Number(e.dataset.diid);}function set_downloaded(e) {if (!e)return;e.classList.add('di-done')const i = e.querySelector('i.fa-download');if (i) {i.classList.remove('fa-download');i.classList.add('fa-check-circle');}}function set_undownloaded(e) {if (!e)return;e.classList.remove('di-done')const i = e.querySelector('i.fa-check-circle');if (i) {i.classList.remove('fa-check-circle');i.classList.add('fa-download');}}function add(id) {if (!set.has(id)) {set.add(id);set_downloaded(map[id]);}}function remove(id) {if (set.has(id)) {set.remove(id);set_undownloaded(map[id]);}}const port_input = document.createElement('input');port_input.type = 'number';port_input.min = 1;port_input.max = 65535;port_input.classList.add('di-input');port_input.placeholder = 'obdi Port'const stored_port = localStorage.getItem('di_port');port_input.value = stored_port ? stored_port : port_default.toString();const status = document.createElement('a');status.classList.add('di-status');status.href = obdi_page;status.target = '_blank';function on_message(e) {let removing = false;const data = e.data.split(' ');console.log('received', data.length, 'commands');for (const s of data) {const id = Number(s);if (id)(removing ? remove : add)(id);else if (s == '+')removing = false;else if (s == '-')removing = true;else {for (const id of set)set_undownloaded(map[id]);set.clear();}}}function on_open() {status.innerText = 'obdi Connected';}let socket = null, tryer = 0, tryer_cnt = 0;function disconnect() {if (socket) {socket.onmessage = socket.onopen = socket.onclose = null;socket.close()}}function retry(id) {if (tryer != id)return;console.log(id, 'retrying', socket, socket ? socket.readyState : 'qwq');const state = socket ? socket.readyState : null;if (state == WebSocket.OPEN)return tryer = 0;if (state != WebSocket.CONNECTING) {disconnect();socket = new WebSocket('ws://127.0.0.1:' + port_input.value);socket.onmessage = on_message;socket.onopen = on_open;socket.onclose = connect;}setTimeout(() => retry(id), 1000);}function connect() {if (tryer)return;status.innerText = 'obdi Disconnected';tryer = ++tryer_cnt;console.log('connects', tryer);retry(tryer);}port_input.onchange = function () {console.log('change to', port_input.value, tryer);localStorage.setItem('di_port', port_input.value);if (tryer)tryer = 0;disconnect();const port = Number(port_input.value);if (0 < port && port < 65536)connect();elsestatus.innerText = 'obdi Disconnected';};const observer = new MutationObserver(function (muts) {for (const mut of muts)for (const node of mut.addedNodes)for (const e of node.querySelectorAll('div.beatmapsets__item')) {const id = get_id(e);if (id && set.has(id))set_downloaded(e);}});// Store global elements to make init_dom idempotent, as navi_observer can be invoked// multiple times when navigating.let topbar = null;function attach_topbar() {const n = document.querySelector('div.nav2__colgroup');if (topbar == n) {return;}if ((topbar = n)) {topbar.appendChild(port_input);topbar.appendChild(status);}}let root = null;function start_observe() {const n = document.querySelector('div.osu-layout__row');if (root == n) {return;}root = n;observer.disconnect();console.log('observing', root);if (root) {observer.observe(root, {childList: true, subtree: true});}}function init_dom() {attach_topbar();start_observe();}const navi_observer = new MutationObserver(init_dom);window.addEventListener('load', function () {document.head.appendChild(css);init_dom();// turbolinks does navigation by replacing the <body>, which invalidates the old observer.// Observe childList of <html> to detect this.navi_observer.observe(document.querySelector('html'), {childList: true});for (const e of document.querySelectorAll('div.beatmapsets__item'))get_id(e);connect();});})();