// ==UserScript== // @name OsuTopPlaysTimeGraph // @namespace https://github.com/Magnus-Cosmos // @version 1.0.1 // @description Adds graphing for which hours top plays were set at // @author Magnus Cosmos // @match https://osu.ppy.sh/users/* // @require https://greasyfork.org/scripts/441005-osuweb/code/OsuWeb.js // @require https://greasyfork.org/scripts/441010-osupageobserver/code/OsuPageObserver.js // @require https://cdn.jsdelivr.net/npm/chart.js@3.7.1/dist/chart.min.js // @downloadURL none // ==/UserScript== const web = new Web(); $(document.head).append($("").html( ` .graph-button { display: inline-block; right: 40px; position: absolute; } .graph-close { background-color: hsl(var(--hsl-red-3)) !important; } .graph-close:hover { background-color: hsl(var(--hsl-red-2)) !important; } .graph-container { padding: 10px; border-radius: 10px; background-color: hsl(var(--hsl-b3)); } #best-plays-graph { display: block; } ` )); function staticPage() { [this.type, this.id] = location.pathname.split("/").slice(1).map(val => { const int = parseInt(val); return val == int ? int : val; }); } function beatmapHandler(beatmapset) { const [currMode, currBeatmap] = location.hash.replace("#", "").split("/").map(val => { const int = parseInt(val); return val == int ? int : val; }); const beatmap = beatmapset.beatmaps.find((b) => { if (b.id === currBeatmap) { return b; } }); $(".beatmap-basic-stats__entry span").last().text(beatmap.count_spinners); } const osuWebObserver = new OsuWebObserver(staticPage, function() { switch(this.type) { case "users": { const bestPlaysH3 = $(".title--page-extra-small").toArray().find(val => { if (val.innerText.includes("Best Performance")) { return val; } }); $(bestPlaysH3).css("display", "inline-block"); $(``).insertAfter(bestPlaysH3); const userId = this.id; $(".graph-button").on("click", function() { $(this).children().first().css("display", "inline-flex"); $(this).children().last().css("visibility", "hidden"); web.get(`/users/${userId}/scores/best`, { mode: "osu", limit: 100 }, res => { res.json().then(data => { const scores = data; const bins = hourBins(scores); $(`
`).append(``).insertAfter(this); graph("best-plays-graph", bins); $(this).children().first().removeAttr("style"); $(this).children().last().removeAttr("style"); $(this).find(".show-more-link__label-text").text("close graph"); $(this).addClass("graph-close"); $(this).unbind("click"); $(this).on("click", function() { const hidden = $(".graph-container").css("display") === "none"; if (hidden) { $(this).find(".show-more-link__label-text").text("close graph"); $(this).addClass("graph-close"); $(".graph-container").show(); } else { $(this).find(".show-more-link__label-text").text("show graph"); $(this).removeClass("graph-close"); $(".graph-container").hide(); } }); }).catch(err => { $(this).children().first().removeAttr("style"); $(this).children().last().removeAttr("style"); console.log(err); }); }, { credentials: "include" }); // for restricted users (need credentials to get top plays) }); } } }); function hourBins(scores) { const bins = scores.reduce((obj, score) => { const date = new Date(score.created_at); const hour = date.getHours(); obj[hour]++; return obj; }, Object.fromEntries([...Array(24).keys()].map(k => [k, 0]))); return Object.entries(bins).map(([k, v]) => { return { x: k, y: v }; }); } function graph(id, data) { const ctx = document.getElementById(id).getContext("2d"); const font = "Torus,Inter,Helvetica Neue,Tahoma,Arial,Hiragino Kaku Gothic ProN,Meiryo,Microsoft YaHei,Apple SD Gothic Neo,sans-serif"; const chartAreaBorder = { id: "chartAreaBorder", beforeDraw(chart, args, options) { const {ctx, chartArea: {left, top, width, height}} = chart; ctx.save(); ctx.strokeStyle = options.borderColor; ctx.lineWidth = options.borderWidth; ctx.setLineDash(options.borderDash || []); ctx.lineDashOffset = options.borderDashOffset; ctx.strokeRect(left, top, width, height); ctx.restore(); } }; return new Chart(ctx, { type: "bar", data: { datasets: [{ data: data, backgroundColor: [ "rgba(31, 119, 180, 0.5)", ], barPercentage: 1, categoryPercentage: 0.96, borderRadius: 4, borderWidth: 1 }] }, options: { responsive: true, layout: { padding: { right: 10 } }, scales: { x: { title: { display: true, text: "Hour of the day", color: "rgba(255,255,255,0.6)", font: { family: font, weight: 400, size: 14 } }, ticks: { offset: false, color: "rgba(255,255,255,0.4)", font: { family: font, weight: 100 } }, offset: true, grid: { offset: true, drawBorder: false } }, y: { title: { display: true, text: "# of top plays set", color: "rgba(255,255,255,0.6)", font: { family: font, weight: 400, size: 14 } }, ticks: { color: "rgba(255,255,255,0.4)", font: { family: font, weight: 100 }, callback: (label, index, labels) => { if (Math.floor(label) === label) { return label; } } }, grid: { drawBorder: false } } }, plugins: { legend: { display: false }, tooltip: { enabled: false }, chartAreaBorder: { borderColor: "rgba(0,0,0,0.25)", borderWidth: 1 } } }, plugins: [chartAreaBorder] }); }