HLTB Rank Games

Organize games by rating for completion page grid view

// ==UserScript==
// @name         HLTB Rank Games
// @namespace    https://github.com/refatK
// @version      0.2
// @description  Organize games by rating for completion page grid view
// @author       RefatK
// @license      MIT
// @match        https://howlongtobeat.com/user/*/games/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=howlongtobeat.com
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const YEAR_FILTER = new Date().getFullYear().toString();
    var currentRatingSplitter = "/";

    function getAllGameEls() {
        return document.querySelectorAll(".UserGameListBox_box__yEG39");
    }

    function mouseEventAllGames(mouseEventName) {
        var event = new MouseEvent(mouseEventName, {
        'view': window,
        'bubbles': true,
        'cancelable': true
        });

        getAllGameEls().forEach(el => el.dispatchEvent(event));
    }

    function mouseOverAllGames() {
        mouseEventAllGames('mouseover');
    }

    function rankAllGames() {
        const gamesContainer = document.getElementById('user_games');
        const games = document.querySelectorAll('.UserGameListBox_info__aZLZy');

        // Group games by their ratings
        const gamesByRating = {};

        games.forEach(game => {
            const infoHtml = game.querySelector('.UserGameListBox_data_display__gF1gE').innerHTML;

            // Only games in year
            const yearToRank = document.getElementById('rankYear').value || YEAR_FILTER;
            if (infoHtml.includes(", " + yearToRank) === false) return;

            const rating = infoHtml.split("Rated</strong>")[1];
            currentRatingSplitter = rating.includes("%") ? "%" : "/";

            if (!gamesByRating[rating]) {
                gamesByRating[rating] = [];
            }

            gamesByRating[rating].push(game.closest('.UserGameListBox_user_game_col__oRflJ'));
        });

        // Create grid sections for each rating
        let gamesByRatingContainerEl = document.querySelector("#gamesByRating");
        if (!gamesByRatingContainerEl) {
            gamesByRatingContainerEl = document.createElement("div");
            gamesByRatingContainerEl.id = "gamesByRating";
            gamesByRatingContainerEl.style.wordSpacing = "10px"
        }
        // gamesByRatingContainerEl.classList.add('UserGameList_user_collection__uQNlh');
        document.getElementById('user_games').after(gamesByRatingContainerEl);

        const gamesByRatingContainer = document.getElementById('gamesByRating');

        const sortedRatings = Object.keys(gamesByRating).sort((a, b) => parseInt(b.split(currentRatingSplitter)[0]) - parseInt(a.split(currentRatingSplitter)[0]));
        for (const rating of sortedRatings) {
            const ratingClass = 'rating-' + rating.split(currentRatingSplitter)[0];

            let gamesGrid = document.querySelector("." + ratingClass);
            const ratingSectionAlreadyExists = !!gamesGrid;
            if (!gamesGrid) {
                gamesGrid = document.createElement("div");
                gamesGrid.classList.add('grid-container');
                gamesGrid.classList.add(ratingClass);
            }

            const gamesList = gamesByRating[rating];
            gamesList.forEach(gameBoxDiv => {
                gamesGrid.appendChild(gameBoxDiv);
                gamesGrid.appendChild(document.createTextNode(" "));
            });

            if (!ratingSectionAlreadyExists) {
                const ratingSection = document.createElement('div');
                ratingSection.innerHTML = `<br><h2>Rating: ${rating}</h2>`;
                ratingSection.appendChild(gamesGrid);
                gamesByRatingContainer.appendChild(ratingSection);
            }
        }
    }

    // 1: add Show Ranks button
    let navBtnDiv = document.querySelector('[id^="lists_user_nav_toggle"]');
    let genRanksBtn = document.createElement("button");
    genRanksBtn.classList.add(...["form_button", "back_orange"]);
    genRanksBtn.innerText = "Show Ranks for";
    genRanksBtn.onclick = function() {
        mouseOverAllGames();
        console.log("waiting");
        setTimeout(function() {
            console.log("ranking now");
            rankAllGames();
            setTimeout(function() {
                mouseEventAllGames('mouseout');
            }, 200);
        }, 200);
    };
    navBtnDiv.appendChild(genRanksBtn);

    let yearInput = document.createElement("input");
    yearInput.id = "rankYear";
    yearInput.type = "number";
    yearInput.value = YEAR_FILTER;
    yearInput.size = 4;
    navBtnDiv.appendChild(yearInput);
})();