OsuTopPlaysTimeGraph

Adds graphing for which hours top plays were set at

目前为 2022-03-12 提交的版本。查看 最新版本

// ==UserScript==
// @name         OsuTopPlaysTimeGraph
// @namespace    https://github.com/Magnus-Cosmos
// @version      1.0.0
// @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/[email protected]/dist/chart.min.js
// ==/UserScript==

const web = new Web();

$(document.head).append($("<style class='circlescript-style'></style>").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");
            $(`<button type="button" class="show-more-link show-more-link--profile-page graph-button">
                <span class="show-more-link__spinner">
                    <div class="la-ball-clip-rotate"></div>
                </span>
                <span class="show-more-link__label">
                    <span class="show-more-link__label-text">graph</span>
                </span>
            </button>`).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);
                        $(`<div class="graph-container"></div>`).append(`<canvas id="best-plays-graph"></canvas>`).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: true }); // 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]
    });
}