显示Steam游戏在线人数

在Steam商店页右侧信息面板增加在线人数, 便于观察游戏的实际热度

// ==UserScript==
// @name         显示Steam游戏在线人数
// @description  在Steam商店页右侧信息面板增加在线人数, 便于观察游戏的实际热度
// @icon         https://store.steampowered.com/favicon.ico
// @version      1.7
// @author       cweijan
// @namespace    cweijan/steam_online_players
// @license      MIT
// @run-at       document-start
// @grant        GM_xmlhttpRequest
// @include      *store.steampowered.com/app/*
// ==/UserScript==

function makeElement(htmlString) {
    var div = document.createElement('div');
    div.innerHTML = htmlString.trim();
    return div.firstChild;
}


let onlineTextNode, onedayPeakTextNode;
let initText = GM_xmlhttpRequest ? '数据加载中' : '无网络权限!', onedayInitText = initText
const appId = location.href.match(/app\/(\d+)/)?.[1];

function colorization(msg, node) {
    let color = '#848683';
    if (Number.isNaN(parseInt(msg))) return color;
    if (msg > 100000) {
        color = '#e72424'
    } else if (msg > 50000) {
        color = '#ce3a3a'
    } else if (msg > 10000) {
        color = '#7cc53f'
    } else if (msg > 3000) {
        color = '#579227'
    }
    if (node) {
        node.setAttribute('style', `color: ${color}`)
    }
    return color;
}

function fillText(msg, type) {
    if (type == 'peak') {
        if (onedayPeakTextNode) {
            onedayPeakTextNode.textContent = msg
            colorization(msg, onedayPeakTextNode)
        }
        else onedayInitText = msg
        return;
    }
    if (onlineTextNode) {
        onlineTextNode.textContent = msg
        colorization(msg, onlineTextNode)
    }
    else initText = msg
}

if (GM_xmlhttpRequest) {
    // 接口参考: https://partner.steamgames.com/doc/webapi/ISteamUserStats#GetNumberOfCurrentPlayers
    GM_xmlhttpRequest({
        method: "GET",
        url: `https://api.steampowered.com/ISteamUserStats/GetNumberOfCurrentPlayers/v1/?appid=${appId}`,
        onload(response) {
            try {
                const playerCount = JSON.parse(response.responseText).response.player_count;
                fillText(playerCount || 0)
            } catch (error) {
                fillText(`解析API返回值失败:${response.responseText}`)
            }
        },
        onerror(response) {
            console.log(response)
            fillText("请求失败")
        }
    })
    GM_xmlhttpRequest({
        method: "GET",
        url: `https://steamcharts.com/app/${appId}`,
        onload(response) {
            try {
                const playerCount = response.responseText.match(/(?<="num">)(\d+)/g)[1];
                fillText(playerCount, 'peak')
            } catch (error) {
                fillText(`解析返回值失败`, 'peak')
            }
        },
        onerror(response) {
            console.log(response)
            fillText("请求失败", 'peak')
        }
    })
}

let addedHistory = false;
const observer = new MutationObserver(mutationList => {
    for (var mutation of mutationList) {
        for (var node of mutation.addedNodes) {
            if (!node.querySelectorAll) continue;
            if (node.getAttribute("class") == 'btn_addtocart' && !addedHistory) {
                addedHistory = true;
                // 增加历史价格记录
                const historyBtnHtml = `<div class="btn_addtocart"><a class="btn_blue_steamui btn_medium" href="https://steamdb.info/app/${appId}/#pricehistory" target="_blank"> <span>查看历史价格</span> </a></div>`
                node.parentNode.insertBefore(makeElement(historyBtnHtml), node)
            } else if (node.getAttribute("id") == 'userReviews') {
                // 增加查看所有评价的链接
                node.appendChild(makeElement(`<a style="padding-left: 104px;" target="_blank" href="https://steamcommunity.com/app/${appId}/reviews?browsefilter=toprated">浏览所有评测</a>`))
                // 当前正在玩
                const html = `<div class="user_reviews">
                                <div class="user_reviews_summary_row">
                                    <div class="subtitle column">在玩人数:</div>
                                    <div class="summary column">
                                        <span id="onlinePlayers" style="color: ${colorization(initText)};">${initText}</span>
                                    </div>
                                </div>
                                <div class="user_reviews_summary_row">
                                    <div class="subtitle column">24小时峰值:</div>
                                        <div class="summary column">
                                        <span id="onedayPeakPlayers" style="color: ${colorization(onedayInitText)};">${onedayInitText}</span>
                                    </div>
                                </div>
                            </div>`
                // node.insertBefore(makeElement(html), node.firstChild)
                node.parentNode.insertBefore(makeElement(html), node)
                onlineTextNode = document.getElementById('onlinePlayers')
                onedayPeakTextNode = document.getElementById('onedayPeakPlayers')
            }
        }
    }
});

observer.observe(document, { childList: true, subtree: true });