VNDB Steam Enhancer

Enhance Steam app pages with data from VNDB.

// ==UserScript==
// @name         VNDB Steam Enhancer
// @namespace    https://vndb.org/
// @version      0.1
// @description  Enhance Steam app pages with data from VNDB.
// @author       Midori Kochiya
// @match        https://store.steampowered.com/app/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=steampowered.com
// @grant        GM_xmlhttpRequest
// @connect      api.vndb.org
// @run-at       document-end
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    const LENGTH_MAP = {
        1: "Very short",
        2: "Short",
        3: "Medium",
        4: "Long",
        5: "Very long"
    };

    function formatMinutes(minutes) {
        if (minutes < 60) {
            return "" + minutes + "m";
        }
        return "" + Math.floor(minutes / 60) + "h" + (minutes % 60) + "m";
    }

    // From SteamDB
    var CurrentAppID;
    function GetAppIDFromUrl( url )
    {
        const appid = url.match( /\/(?:app|sub|bundle|friendsthatplay|gamecards|recommended|widget)\/(?<id>[0-9]+)/ );

        return appid ? parseInt( appid.groups.id, 10 ) : -1;
    }
    function GetCurrentAppID()
    {
        if( !CurrentAppID )
        {
            CurrentAppID = GetAppIDFromUrl( location.pathname );
        }

        return CurrentAppID;
    }

    function makeRow(rowClass, subtitle, linkText, linkUrl) {
        const row = document.createElement("div");
        row.className = "dev_row " + rowClass;

        const subtitleEl = document.createElement("div");
        subtitleEl.className = 'subtitle column';
        subtitleEl.textContent = subtitle;

        let linkEl;
        if (linkUrl) {
            linkEl = document.createElement("a");
            linkEl.className = "date";
            linkEl.textContent = linkText;
            linkEl.href = linkUrl;
        } else {
            linkEl = document.createElement("div");
            linkEl.className = "date";
            linkEl.textContent = linkText;
        }

        row.appendChild(subtitleEl);
        row.appendChild(linkEl);

        return row;
    }

    let appId = GetCurrentAppID();
    if (appId == -1) {
        return;
    }

    GM_xmlhttpRequest({
        method: "POST",
        url: "https://api.vndb.org/kana/vn",
        data: JSON.stringify({
            "filters": ["release", "=", ["extlink", "=", ["steam", appId]]],
            "fields": "length,length_votes,length_minutes,rating,votecount"
        }),
        headers: {
            "Content-Type": "application/json"
        },
        onload: function(response) {
            let result = JSON.parse(response.responseText);
            if (!result.results || result.results.length == 0) {
                return;
            }

            let item = result.results[0];

            const vndbIdRow = makeRow(
                "vndb_id",
                "VNDB",
                item.id,
                "https://vndb.org/" + item.id
            );

            const rows = [
                vndbIdRow,
            ];

            if (item.rating) {
                rows.push(makeRow(
                    "vndb_rating",
                    "VNDB Rating",
                    "" + item.rating + " (" + item.votecount + ")"
                ));
            }

            if (item.length) {
                let lengthText = LENGTH_MAP[item.length];

                if (item.length_minutes && item.length_votes) {
                    lengthText += " (" + formatMinutes(item.length_minutes) + " from " + item.length_votes + " votes)";
                }

                rows.push(makeRow(
                    "vndb_length",
                    "VNDB Length",
                    lengthText
                ));
            }

            // Reverse due to how they are inserted
            rows.reverse();
            const releaseDate = document.querySelector('.release_date');

            if( releaseDate ){
                for (const el of rows) {
                    releaseDate.parentNode.insertBefore(el, releaseDate.nextSibling);
                }
            } else {
                const firstDevRow = document.querySelector( '.glance_ctn_responsive_left .dev_row' );
                if(firstDevRow) {
                    for (const el of rows) {
                        firstDevRow.parentNode.insertBefore(el, firstDevRow);
                    }
                }
            }
        }
    });
})();