Steam赛博父子鉴定 (游戏库蓝绿|一键私密|库存价值统计)

游戏库蓝绿|一键私密|库存价值统计

// ==UserScript==
// @name         Steam赛博父子鉴定 (游戏库蓝绿|一键私密|库存价值统计)
// @license      MIT
// @namespace    http://tampermonkey.net/
// @version      0.4.1
// @description  游戏库蓝绿|一键私密|库存价值统计
// @author       Rawwiin
// @match        https://steamcommunity.com/id/*/games/*
// @match        https://steamcommunity.com/id/*/games?*
// @match        https://steamcommunity.com/profiles/*/games/*
// @match        https://steamcommunity.com/profiles/*/games?*
// @icon      	 https://store.steampowered.com/favicon.ico
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_notification
// @grant        GM_info
// @run-at       document-end
// ==/UserScript==
// TODO
// BUG
// 过滤游戏卡顿

const url_my_wishlist = "https://steamcommunity.com/my/wishlist/";
// const wishlistUrl = "https://store.steampowered.com/wishlist"
const url_my_games = "https://steamcommunity.com/my/games?tab=all";
const url_vac_games =
    "https://store.steampowered.com/search/?sort_by=Released_DESC&category1=998&category2=8&ndl=1";
const url_appdetails = "https://store.steampowered.com/api/appdetails/?appids=";
const url_appdetails_price_overview =
    "https://store.steampowered.com/api/appdetails/?filters=price_overview&cc=cn&appids=";
const color_own = "#54662f";
const color_own_sub = color_own; //"#30655f";
const color_wish = "#4c90b6";
const price_gradient = [6, 11, 29, 42, 58, 76, 108, 136, 168, 198, 238, 268];
const price_high = 198;
const price_middle = 76;
const price_low = 29;
const color_price_high = "#ca2842";
const color_price_middle = "#b9a074";
const vacAppidList = [
    655740, 505460, 346330, 559650, 393380, 445220, 629760, 690790, 299740, 221100, 327090, 707010,
    252490, 700330, 476600, 730, 346110, 304930, 436520, 232090, 292730, 324810, 363680, 88801,
    394690, 372000, 451130, 225840, 360940, 394510, 376210, 311210, 290340, 260430, 299360, 321260,
    239140, 90948, 290790, 274940, 209650, 282800, 209160, 222880, 224260, 243800, 570, 222480,
    223710, 104900, 221040, 227100, 212480, 202970, 4920, 215470, 219640, 61730, 214360, 204300,
    212410, 209610, 14770, 201070, 58610, 115300, 65800, 17710, 35450, 63950, 55110, 70000, 63000,
    201270, 55100, 63200, 63500, 42700, 300, 39000, 17570, 550, 10180, 1250, 500, 17500, 469, 440,
    6510, 4000, 2100, 2400, 360, 1200, 320, 240, 80, 30, 40, 10, 60, 50, 20, 70, 1245620, 976730,
    555160, 1888160, 471710, 678950, 1172620, 454650, 671860, 251570, 518150, 823130, 252490,
    1568590, 1268750, 418460, 1372110, 1461600, 1818750, 444090, 1399780, 581320, 552500, 633230,
    266410, 1301210, 1815230, 291550, 447040, 686810, 594650, 393380, 1785150, 1097150, 798510,
    950180, 1824220, 333930, 820520, 1029690, 381210, 544920, 886250, 378860, 1024890, 1928420,
    460930, 438740, 282660, 1961460, 466240, 386360, 761890, 626690, 383120, 1172470, 753650,
    1957780, 884660, 1575680, 2087030, 1377380, 299360, 706220, 307950, 715220, 895400, 1133060,
    961200, 315210, 1556200, 393420, 327690, 813820, 1650010, 915810, 573100, 736220, 2073850,
    582660, 760160, 2152790, 909750, 438100, 872200, 513710, 918570, 1789480, 1241100, 1049800,
    651150, 1433140, 1121710, 714080, 1293660, 816020, 973580, 301520, 1956740, 964440, 1371580,
    783770, 2221490, 1599340, 473690, 1262240, 1240440, 2051120, 1063730, 215100, 719890, 707010,
    1194810, 269210, 304390, 1097840, 1227800, 2285150, 766370, 1286320, 1635450, 1008580, 715400,
    1163550, 1473640, 1361210, 674690, 1607250, 835860, 1222730, 625340, 924970, 1294660, 299740,
    611020, 1780330, 1963590, 520530, 1493750, 876460, 1189800, 381990, 1058650, 641080, 611360,
    1566880, 322780, 236390, 1501750, 920690, 519190, 2000590, 1180380, 903950, 556440, 647590,
    1436900, 1009290, 1233550, 314430, 1054690, 1186040, 1170950, 644290, 1913210, 1183940, 1903270,
    1527890, 293220, 939510, 1442910, 1106750, 906930, 1132210, 622170, 880850, 1206370, 437220,
    344760, 867400, 1226470, 594770, 2170420, 1276760, 2064870, 377140, 1816670, 547860, 2119490,
    1147660, 991560, 751240, 1254120, 304030, 1093170, 2000270, 375230, 922620, 656240, 927350,
    1171690, 653120, 1473480, 1408680, 805110, 819500, 2083800, 1412620, 738530, 1765520, 1769930,
    1057240, 993870, 889750, 1133430, 1991140, 1419640, 331810, 752720, 1367080, 1657090, 202090,
    421650, 419520, 42160, 584210, 738090, 1611740, 585030, 234530, 804810, 1478540, 499470,
    1904230, 2719160, 1638720, 1205550, 709310, 2076040, 942810, 1271810, 819970, 2156210, 1692190,
    596350, 763420, 719690, 923330, 770720, 1724660, 700580, 768350, 912290, 1888340, 1530870,
    1004390, 844630, 1681770, 759510, 774941, 1487410, 737010, 2089760, 2023760, 1661320, 1793660,
    654310, 878760, 737230, 858460, 1504620, 1692200, 1581460, 1504860, 468220, 1843080, 1867420,
    1477610, 1829770, 1958370, 2222330, 1725420, 1585520, 2053090, 2324560, 2572270, 1940990,
    2321720, 1875460, 1666300, 2654250, 2429660, 2427520, 1469120, 2842380, 1930870, 1189980,
    2337480, 2231910, 1532550, 2699000, 2437010, 2287610, 1176940, 2790800, 2713510, 2383590,
    2338060, 2313860, 2258030, 2236250, 2220430, 2125710, 1993280, 1948950, 377610, 2275170,
    2101000, 2855320, 2818760, 2790880, 2781480, 2633570, 2611730, 2549560, 2542690, 2464790,
    2295890, 2291900, 2259670, 2240860, 2090780, 2069210, 2021400, 2011600, 1826330, 1781970,
    1655340, 1641290, 1639880, 1622200, 1582170, 1472900, 1435990, 1426320, 1368990, 1332530,
    1326050, 1318350, 1297110, 1281720, 1270350, 1265940, 1172630, 1150000, 1143930, 1085780,
    1067170, 1064780, 1064280, 996600, 972310, 924720, 920720, 911460, 882430, 845260, 844650,
    822500, 803370, 746200, 743300, 737770, 689410, 640100, 579850, 568660, 550790, 541790, 524930,
    459430, 418480, 405100, 381690, 369520, 349360, 347130, 331370, 258550, 304930, 222880, 33930,
    107410, 359550, 346110, 2290180, 447820, 646910, 1520470, 218230, 1085660, 761890, 440900,
    550650, 221100, 209870, 1083500, 747610, 1872760, 227940, 362300, 1934780, 407530, 1549180,
    1874880, 1263550, 868270, 840800, 436520, 295110, 2399830, 578080, 433850, 764920, 355840,
    433350, 439700, 530700, 815940, 1443350, 834910, 714210, 987350, 681660, 895970, 619080,
    1372880, 555440, 513650, 459510, 1619990, 622590, 623990, 813000, 1600360, 931180, 994440,
    802240, 1890860, 2325320, 2430930, 2358330, 1382120, 1075340, 1024020, 1006030, 975570, 950520,
    845220, 764940, 764190, 760760, 667950, 459430, 445400, 407660, 376030, 960170, 1477590,
    1049590, 1827180, 239660, 439350, 582660, 237310, 1377580, 339610, 11610, 1056640, 286940,
    212390, 1011810, 454910, 1426440, 871120, 537180, 790650, 1826980, 253490, 1153700, 836620,
    1185560, 361800, 1825750, 337410, 1292630, 542590, 2371630, 1531430, 1218740, 747970, 349720,
    300970, 2356310, 921940, 2088180, 1132210, 2208530, 518660, 280620, 1287290, 1226470, 747920,
    1379750, 662320, 1674470, 2720700, 2229260, 389430, 1258320, 2170050, 1689070, 2639000, 1692070,
    1175210, 1524370, 681660, 1130700, 2258870, 369200, 223650, 102700, 360760, 356330, 1688720,
    389300, 591530, 109400, 624910, 568810, 1840560, 212240, 1658740, 1695900, 1829180, 1524240,
    2457550, 1804190, 696260, 2696470, 2710780, 1940810, 1874390, 754420, 516080, 2340400, 553850,
    2167580, 550650, 1608220, 558230, 354290, 1056640, 212390, 371310, 2426960, 1011810, 838330,
    890400, 1094710, 253490, 390100, 442080, 675560, 1338610, 539650, 1934850, 1232420, 1263550,
    295950, 1563940, 1674340, 1218740, 350700, 390090, 2356310, 921940, 1342630, 1630280, 721030,
    905640, 1226470, 536950, 1711430, 433350, 357500, 1549250, 2377950, 1863390, 949690, 1687760,
    874240, 548290, 2552340, 2361570, 1574360, 610940, 658510, 895970, 2016940, 102700, 315640,
    317110, 218470, 267790, 410560, 481890, 224320, 29520, 1952710, 2595550, 2208510, 2081110, 2200,
    2620, 7940, 21090, 2630, 35450, 10090, 9010, 48190, 9050, 1238860, 2210, 24960, 201870, 2640,
    17330, 15120, 32770, 13520, 9070, 24840, 17300, 203290, 13540, 19900, 324830, 17430, 47790,
    324850, 208480, 13140, 1238880, 1238820, 212630, 9460, 24860, 2350, 236830, 47830, 10170, 17340,
    3970, 21110, 21120, 211880, 10000, 32690, 32700, 7950, 238690, 212542, 203300, 13180, 10050,
    10030, 10010, 212200, 1184140, 212160, 273110, 1934780, 372000, 216150, 495910, 2107670,
    1175730, 2178420, 350280, 369200, 560380, 591530, 212180, 2074930, 927130, 1194260, 1979310,
    2107680, 2585120, 2119720, 2396970, 516080, 1671200, 1008080, 1672740, 1913730, 1364020,
    1881610, 1674340, 1969870, 1881700, 1668940, 1282270, 2009240, 2430450, 2541620, 1676000,
    2700330, 2581010, 384030, 231060, 212370, 212180, 221080, 212220, 238110, 1922560, 1811260,
    2195250, 2140330, 1517290, 2434630, 1010270, 555570, 1240290, 226700, 755790, 1674340, 994560,
    955100, 677620, 731620, 1990390, 1938090, 2075730, 1240410,
];

var isMarkOwn = true;
var isMarkWish = true;
var isHideOwn = false;
var isHideNotWish = false;
var shownCount = 0;

var myAppidList;
var mySubAppidList;
var myWishAppidList;
var hisAppidList;
var appidPrice = {};
var hisStrProfileName;

var gameListObserver;
var mySubProfileShowText = "";

var interval = 2000;
var retry = 200;

var domParser = new DOMParser();
var ico_vac = "https://store.akamai.steamstatic.com/public/images/v6/ico/ico_vac.png";
(function () {
    "use strict";
    console.log("开始鉴定...");
    // GM_xmlhttpRequest({
    //     method: "POST",
    //     url: "https://api.steampowered.com/IAccountPrivateAppsService/ToggleAppPrivacy/v1?access_token=655ca0ce4295bfaf5c40c4c9260a896d&spoof_steamid=",
    //     data: "input_protobuf_encoded=CLz/HxAB",
    //     onload: function (xhr) {
    //         console.log(xhr);
    //     },
    // });
    // getAppDetails(730);

    init();
})();

function init() {
    clear();

    let persona_name_text_content = document.getElementsByClassName(
        "whiteLink persona_name_text_content"
    );
    if (persona_name_text_content && persona_name_text_content.length) {
        hisStrProfileName = persona_name_text_content[0].textContent
            ? persona_name_text_content[0].textContent.trim()
            : "";
    }

    let account_pulldown = document.getElementById("account_pulldown");
    let myStrProfileName =
        account_pulldown && account_pulldown.textContent ? account_pulldown.textContent.trim() : "";
    // DEBUG
    // if (true) {
    if (myStrProfileName && myStrProfileName == hisStrProfileName) {
        loadHisGameList().then(() => {
            addStatusBar(true);
            addGameListObserver(500);
        });
        return;
    } else if (!myStrProfileName || myStrProfileName != GM_getValue("myStrProfileName")) {
        // 切换账号
        GM_deleteValue("myStrProfileName");
        GM_deleteValue("myAppidList");
        GM_deleteValue("myWishAppidList");
    }
    addSectionTabListener();
    const myGamesPromise = loadMyGameList();
    myGamesPromise.then(() => {
        if (myStrProfileName) {
            GM_setValue("myStrProfileName", myStrProfileName);
        }
    });
    const myWishPromise = loadMyWishlist();
    const hisGameListPromise = loadHisGameList();
    const mySubGamesPromise = loadMySubGameList();
    Promise.all([myGamesPromise, myWishPromise, hisGameListPromise, mySubGamesPromise])
        .then(() => {
            refreshGameDivList();
            addStatusBar();
            addGameListObserver(500);
        })
        .catch((error) => {
            console.error(error);
        });
}

function refresh() {
    refreshGameDivList();
    refreshStatusBar();
}

function clear() {
    hisAppidList = null;
    removeStatusBar();
    // if (gameListObserver) {
    //     gameListObserver.disconnect();
    //     gameListObserver = null;
    // }
}

function loadMyGameList() {
    return new Promise((resolve, reject) => {
        if ((myAppidList = GM_getValue("myAppidList")) && myAppidList.length) {
            console.log("缓存加载我的游戏", myAppidList.length);
            resolve();
            return;
        }
        getAppidListFromGamePage(url_my_games).then((appidList) => {
            myAppidList = appidList;
            // 缓存
            GM_setValue("myAppidList", myAppidList);
            console.log("加载我的游戏", myAppidList && myAppidList.length);
            resolve();
        });
    });
}

function getAppidListFromGamePage(url) {
    return new Promise((resolve, reject) => {
        if (!url) {
            resolve([]);
        }
        load(url, (res) => {
            let doc = domParser.parseFromString(res, "text/html");
            let dataProfileGameslist = getDataProfileGameslist(doc);
            let rgGames = getRgGames(dataProfileGameslist);
            let appidList = rgGames ? rgGames.map((game) => game.appid) : [];
            resolve(appidList);
        });
    });
}

function loadMySubGameList(subProfileListStr) {
    return new Promise((resolve, reject) => {
        mySubAppidList = [];
        let mySubProfile = GM_getValue("mySubProfile");
        if (!mySubProfile) {
            mySubProfile = {};
        }
        if (subProfileListStr) {
            let promises = [];
            const id64Rgx = /^\d{17}$/;
            subProfileListStr.split(",").forEach((subProfile) => {
                if (!subProfile) {
                    return;
                }
                let promise = getAppidListFromGamePage(
                    "https://steamcommunity.com/" +
                        (id64Rgx.test(subProfile) ? "profiles" : "id") +
                        "/" +
                        subProfile +
                        "/games/?tab=all"
                );
                promise.then((appidList) => {
                    mySubProfile[subProfile] = appidList;
                });
                promises.push(promise);
            });
            Promise.all(promises).then(() => {
                loadMySubAppidList(mySubProfile);
                resolve();
            });
        } else {
            loadMySubAppidList(mySubProfile);
            resolve();
        }
    });
}

function loadMySubAppidList(mySubProfile) {
    mySubProfileShowText = "";
    mySubAppidList = [];
    for (let subProfile in mySubProfile) {
        let appidList = mySubProfile[subProfile];
        appidList.forEach((appid) => {
            if ((!myAppidList || !myAppidList.includes(appid)) && !mySubAppidList.includes(appid)) {
                mySubAppidList.push(appid);
            }
        });
        if (mySubProfileShowText) {
            mySubProfileShowText += " | ";
        } else {
            mySubProfileShowText = "小号(游戏数):";
        }
        mySubProfileShowText += subProfile + "(" + appidList.length + ")";
    }
    console.log("加载小号的游戏", mySubAppidList && mySubAppidList.length);
    GM_setValue("mySubProfile", mySubProfile);
}

function loadMyWishlist() {
    return new Promise((resolve, reject) => {
        if ((myWishAppidList = GM_getValue("myWishAppidList")) && myWishAppidList.length) {
            console.log("缓存加载我的愿望单", myWishAppidList.length);
            resolve();
            return;
        }
        load(url_my_wishlist, (res) => {
            let doc = domParser.parseFromString(res, "text/html");
            let myRgWishlistData = rgWishlistData(doc);
            myWishAppidList = myRgWishlistData ? myRgWishlistData.map((game) => game.appid) : [];
            GM_setValue("myWishAppidList", myWishAppidList);
            console.log("加载我的愿望单", myWishAppidList && myWishAppidList.length);
            // myWishAppidList = getAppids(res);
            // console.log("加载我的愿望单", myWishAppidList.length);
            resolve();
        });
    });
}

function loadHisGameList() {
    return new Promise((resolve) => {
        let count = 0;
        const intervalId = setInterval(() => {
            if (count++ > retry) {
                // 结束定时器
                clearInterval(intervalId);
                resolve();
                return;
            }
            let hisDataProfileGameslist = getDataProfileGameslist(document);
            if (hisDataProfileGameslist) {
                let hisRgGames = getRgGames(hisDataProfileGameslist);
                hisAppidList = hisRgGames ? hisRgGames.map((game) => game.appid) : [];
                console.log("加载TA的游戏", hisAppidList && hisAppidList.length);
                if (hisAppidList && hisAppidList.length) {
                    let appids = "";
                    let requestPromiseArray = [];
                    for (let i = 0; i < hisAppidList.length; i++) {
                        let appid = hisAppidList[i];
                        if (appids) {
                            appids += ",";
                        }
                        appids += appid;

                        if (i + 1 == hisAppidList.length || appids.length >= 1900) {
                            let request = getAppDetails(url_appdetails_price_overview, appids);
                            request.then((appdetails) => {
                                for (let key in appdetails) {
                                    let price_overview;
                                    if (
                                        appdetails[key] &&
                                        appdetails[key].data &&
                                        (price_overview = appdetails[key].data.price_overview)
                                    ) {
                                        appidPrice[key] = price_overview.initial;
                                    }
                                }
                            });
                            requestPromiseArray.push(request);
                            appids = "";
                        }
                    }
                    Promise.all(requestPromiseArray).then(() => {
                        refresh();
                    });
                }
                clearInterval(intervalId);
                resolve();
            }
        }, interval);
    });
}
``;

function addStatusBar(isSelfPage) {
    let count = 0;
    const intervalId = setInterval(() => {
        if (count++ > retry) {
            clearInterval(intervalId);
            return;
        }

        // let element = document.getElementsByClassName("_2_BHLBIwWKIyKmoabfDFH-");
        let element = document.getElementsByClassName("_3tY9vKLCmyG2H2Q4rUJpkr ");
        if (element && element.length) {
            removeStatusBar();
            // style='display: flex;flex-wrap: wrap;justify-content:space-between;align-items:center;'
            //  style='display: grid;grid-template-columns: auto auto auto 1fr;justify-items: start;'
            if (isSelfPage) {
                let html =
                    "<div class='cyberFatherStatusBar'>" +
                    "<div style='display: grid;grid-template-columns: auto auto auto auto auto 1fr;justify-items: start;'>" +
                    '<button id="privateMPGames" class="privateGames" style="background:transparent;color:#199FFF;border:none;cursor: pointer;">私密多人游戏</button>' +
                    '<button id="privatePVPOLGames" class="privateGames" style="margin-left: 10px;background:transparent;color:#199FFF;border:none;cursor: pointer;">私密线上玩家对战游戏</button>' +
                    '<button id="privateVacGames" class="privateGames" style="margin-left: 10px;background:transparent;color:#199FFF;border:none;cursor: pointer;">私密VAC/AntiCheat游戏</button>' +
                    '<button id="privateAllGames" class="privateGames" style="margin-left: 10px;background:transparent;color:#199FFF;border:none;cursor: pointer;">私密所有游戏</button>' +
                    '<button id="unprivateAllGames" class="privateGames" style="margin-left: 10px;background:transparent;color:#199FFF;border:none;cursor: pointer;grid-column-end: span 2;">取消所有私密</button>' +
                    // "</div>" +
                    // "<div style='display: grid;grid-template-columns: auto auto auto auto 1fr;justify-items: start;'>" +
                    "<label style='margin-left: 0;display: none;' class='hisWorthDiv'>我的库存价值:" +
                    "<span id='hisWorth'></span>" +
                    "</label>" +
                    "<label style='margin-left: 15px;display: none;' class='hisWorthDiv'>均价:" +
                    "<span id='hisWorthAVG'></span>" +
                    "</label>" +
                    "<label style='margin-left: 15px;display: none;' class='hisWorthDiv'>¥" +
                    price_high +
                    "及以上游戏数:" +
                    "<span id='hisWorthHighNum' style='color:" +
                    color_price_high +
                    "'></span>" +
                    "</label>" +
                    "<label style='margin-left: 15px;display: none;' class='hisWorthDiv'>¥" +
                    price_middle +
                    " ~ ¥" +
                    (price_high - 1) +
                    "游戏数:" +
                    "<span id='hisWorthMiddleNum' style='color:" +
                    color_price_middle +
                    "'></span>" +
                    "</label>" +
                    "<label style='margin-left: 15px;display: none;' class='hisWorthDiv'>¥1 ~ ¥" +
                    price_low +
                    "游戏数:" +
                    "<span id='hisWorthLowNum'></span>" +
                    "</label>" +
                    "<div style='margin-left: 15px;display: none;' class='hisWorthDiv'>" +
                    '<select id="priceSelect"></select>' +
                    '<button id="privatePriceGames" class="privateGames" style="margin-left: 0;background:transparent;color:#199FFF;border:none;cursor: pointer;">私密</button>' +
                    "</div>" +
                    "</div>" +
                    '<span id="privateResult" style="display:none;grid-column-end: span 2;"></span>' +
                    // '<div id="cfOverlay" style="display: none; position: fixed; width: 100%; height: 100%; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,0.5); z-index: 2; cursor: pointer;">' +
                    // "</div>" +
                    "</div>";
                element[0].insertAdjacentHTML("beforebegin", html);
                let priceSelect = document.getElementById("priceSelect");
                if (priceSelect) {
                    let options = '<option value="0">免费游戏</option>';
                    for (let i = 0; i < price_gradient.length; i++) {
                        let price = price_gradient[i];
                        options +=
                            '<option value="' + price * 100 + '">¥1 ~ ¥' + price + "</option>";
                    }
                    priceSelect.innerHTML = options;
                }

                let privateGamesEles = document.getElementsByClassName("privateGames");
                if (privateGamesEles && privateGamesEles.length) {
                    for (let i = 0; i < privateGamesEles.length; i++) {
                        const ele = privateGamesEles[i];
                        let eleId = ele.getAttribute("id");
                        ele.addEventListener("click", function () {
                            switch (eleId) {
                                case "privateMPGames":
                                    privateGames(true, null, [1, 8, 36]);
                                    break;
                                case "privatePVPOLGames":
                                    privateGames(true, null, [8, 36]);
                                    break;
                                case "privateVacGames":
                                    // getVacAppidList().then((vacAppidList) => {
                                    //     privateGames(true, vacAppidList);
                                    // });
                                    privateGames(true, vacAppidList);
                                    break;
                                case "privateAllGames":
                                    privateGames(true);
                                    break;
                                case "unprivateAllGames":
                                    privateGames(false);
                                    break;
                                case "privatePriceGames":
                                    let price = priceSelect.value;
                                    if (price != null) {
                                        privateGames(true, null, null, price);
                                    }
                                    break;

                                default:
                                    break;
                            }
                        });
                    }
                }
            } else {
                if (!hisAppidList) {
                    clearInterval(intervalId);
                    return;
                }
                let html =
                    "<div class='cyberFatherStatusBar'>" +
                    "<div style='display: grid;grid-template-columns: auto auto auto auto 1fr;justify-items: start;'>" +
                    "<label style='margin-left: 0;'>TA拥有我库存外的游戏:" +
                    "<span id='notHave'></span>" +
                    "</label>" +
                    "<label style='margin-left: 15px;'>TA拥有我愿望单中的游戏:" +
                    "<span id='inWish'></span>" +
                    "</label>" +
                    "<label style='margin-left: 15px;'>鉴定结果:" +
                    "<span id='identify'></span>" +
                    "</label>" +
                    // "<label style='margin-left: 15px; justify-self: end;'>仅显示愿望单中的游戏" +
                    // '<input type="checkbox" name="myCheckbox" value="1" id="checkbox_hideNotWish" style="margin: 3px">' +
                    // "</label>" +
                    "<label style='margin-left: 15px; justify-self: end;grid-column-end: span 2;'>隐藏已拥有的游戏" +
                    '<input type="checkbox" name="myCheckbox" value="1" id="checkbox_hideMine" style="margin: 3px">' +
                    "</label>" +
                    // "</div>" +
                    // "<div style='display: grid;grid-template-columns: auto auto auto auto 1fr;justify-items: start;'>" +
                    "<label style='margin-left: 0;display: none;' class='hisWorthDiv'>TA的库存价值:" +
                    "<span id='hisWorth'></span>" +
                    "</label>" +
                    "<label style='margin-left: 15px;display: none;' class='hisWorthDiv'>均价:" +
                    "<span id='hisWorthAVG'></span>" +
                    "</label>" +
                    "<label style='margin-left: 15px;display: none;' class='hisWorthDiv'>¥" +
                    price_high +
                    "及以上游戏数:" +
                    "<span id='hisWorthHighNum' style='color:" +
                    color_price_high +
                    "'></span>" +
                    "</label>" +
                    "<label style='margin-left: 15px;display: none;' class='hisWorthDiv'>¥" +
                    price_middle +
                    " ~ ¥" +
                    (price_high - 1) +
                    "游戏数:" +
                    "<span id='hisWorthMiddleNum' style='color:" +
                    color_price_middle +
                    "'></span>" +
                    "</label>" +
                    "<label style='margin-left: 15px;display: none;' class='hisWorthDiv'>¥" +
                    price_low +
                    "以下游戏数:" +
                    "<span id='hisWorthLowNum'></span>" +
                    "</label>" +
                    "</div>" +
                    "<div style='display: grid;grid-template-columns: auto auto 1fr;justify-items: start;'>" +
                    '<button id="addSubProfile" style="background:transparent;color:#199FFF;border:none;cursor: pointer;">添加小号</button>' +
                    '<button id="removeSubProfileBtn" style="margin-left: 10px;background:transparent;color:#199FFF;display:none;border:none;cursor: pointer;">清空小号</button>' +
                    "<span id='subProfileListDiv' style='margin-left: 10px;'></span>" +
                    "</div>" +
                    "</div>";
                element[0].insertAdjacentHTML("beforebegin", html);
                refreshStatusBar();
                let checkbox_hideMine = document.getElementById("checkbox_hideMine");
                if (checkbox_hideMine) {
                    checkbox_hideMine.addEventListener("change", function () {
                        isHideOwn = checkbox_hideMine.checked;
                        refreshGameDivList();
                    });
                }
                let checkbox_hideNotWish = document.getElementById("checkbox_hideNotWish");
                if (checkbox_hideNotWish) {
                    checkbox_hideNotWish.addEventListener("change", function () {
                        isHideNotWish = checkbox_hideNotWish.checked;
                        refreshGameDivList();
                    });
                }

                let addSubProfile = document.getElementById("addSubProfile");
                if (addSubProfile) {
                    addSubProfile.addEventListener("click", function () {
                        let addSubProfileListStr = prompt("请输入小号ID或64位ID");
                        if (addSubProfileListStr) {
                            loadMySubGameList(addSubProfileListStr).then(() => {
                                refresh();
                            });
                        }
                    });
                }
                let removeSubProfileBtn = document.getElementById("removeSubProfileBtn");
                if (removeSubProfileBtn) {
                    removeSubProfileBtn.addEventListener("click", function () {
                        GM_deleteValue("mySubProfile");
                        loadMySubGameList().then(() => {
                            refresh();
                        });
                    });
                }
            }
            refreshStatusBar();
            clearInterval(intervalId);
        }
    }, interval);
}

function refreshStatusBar() {
    let subProfileListDiv = document.getElementById("subProfileListDiv");
    if (subProfileListDiv) {
        subProfileListDiv.textContent = mySubProfileShowText ? mySubProfileShowText : "";
    }
    let removeSubProfileBtn = document.getElementById("removeSubProfileBtn");
    if (removeSubProfileBtn) {
        removeSubProfileBtn.style.display = mySubProfileShowText ? "block" : "none";
    }
    let notHave = 0;
    let inWish = 0;
    hisAppidList.forEach(function (appid) {
        if (
            (!myAppidList || !myAppidList.includes(appid)) &&
            (!mySubAppidList || !mySubAppidList.includes(appid))
        ) {
            notHave++;
        }
        if (myWishAppidList && myWishAppidList.includes(appid)) {
            inWish++;
        }
    });
    let notHaveEle = document.getElementById("notHave");
    if (notHaveEle) {
        notHaveEle.textContent = notHave;
    }
    let inWishEle = document.getElementById("inWish");
    if (inWishEle) {
        inWishEle.textContent = inWish;
    }
    let identifyEle = document.getElementById("identify");
    if (identifyEle) {
        identifyEle.textContent = identify();
    }
    if (appidPrice) {
        let hisWorth = 0;
        let highNum = 0;
        let middleNum = 0;
        let lowNum = 0;
        let i = 0;
        for (let key in appidPrice) {
            i++;
            let price = parseInt(appidPrice[key] / 100);
            hisWorth += price;
            if (price >= price_high) {
                highNum++;
            } else if (price >= price_middle) {
                middleNum++;
            } else if (price <= price_low) {
                lowNum++;
            }
        }
        let hisWorthAVG = i ?  parseInt(hisWorth / i) : 0;
        let hisWorthDivs = document.getElementsByClassName("hisWorthDiv");
        if (hisWorth && hisWorthDivs && hisWorthDivs.length) {
            for (let i = 0; i < hisWorthDivs.length; i++) {
                hisWorthDivs[i].style.display = "block";
            }
            let hisWorthSpan = document.getElementById("hisWorth");
            hisWorthSpan && (hisWorthSpan.innerText = "¥ " + hisWorth);
            let hisWorthAVGSpan = document.getElementById("hisWorthAVG");
            hisWorthAVGSpan && (hisWorthAVGSpan.innerText = "¥ " + hisWorthAVG);
            let highNumSpan = document.getElementById("hisWorthHighNum");
            highNumSpan && (highNumSpan.innerText = highNum);
            let middleNumSpan = document.getElementById("hisWorthMiddleNum");
            middleNumSpan && (middleNumSpan.innerText = middleNum);
            let lowNumSpan = document.getElementById("hisWorthLowNum");
            lowNumSpan && (lowNumSpan.innerText = lowNum);
        }
    }
}

function removeStatusBar() {
    let statusBars = document.getElementsByClassName("cyberFatherStatusBar");
    if (statusBars && statusBars.length) {
        for (let i = 0; i < statusBars.length; i++) {
            statusBars[i].remove();
        }
    }
}

function identify() {
    let identity = "";
    let myGameNum = myAppidList ? myAppidList.length : 0;
    let hisGameNum = hisAppidList ? hisAppidList.length : 0;
    if (myGameNum == 0 || hisGameNum == 0) {
        return "无法鉴定";
    }
    let diff = hisGameNum - myGameNum;
    let multi = hisGameNum / myGameNum;
    if (diff == 0) {
        identity = "世另我";
    } else if (hisGameNum <= 10) {
        identity = "老六";
    } else if (myGameNum > 100) {
        if (diff >= 0) {
            if (multi >= 3) {
                identity = "义父";
            } else {
                identity = "义兄";
            }
        } else {
            if (multi >= 0.5) {
                identity = "义弟";
            } else {
                identity = "义子";
            }
        }
    } else {
        if (multi > 5) {
            identity = "义父";
        } else if (diff >= 0) {
            identity = "义兄";
        } else {
            identity = "义弟";
        }
    }

    let describe;
    // if (myAppidList.length >= 10 && hisAppidList.length >= 10) {
    //     let myTop100 = myAppidList.slice(0, 100);
    //     let hisTop100 = hisAppidList.slice(0, 100);
    //     let hisTop10 = hisTop100.slice(0, 10);
    //     // let intersection = new Set(
    //     //     [...myTop100].filter((x) => hisTop100.has(x))
    //     // );
    //     let intersection = 0;
    //     for (let i = 0; i < myTop100.length; i++) {
    //         if (hisTop100.includes(myTop100[i])) {
    //             if (i < 10 && hisTop10.includes(myTop100[i])) {
    //                 intersection += myTop100.length / 10 - i + 1;
    //             } else {
    //                 intersection += 1;
    //             }
    //         }
    //     }
    //     let fact = intersection / myTop100.length;
    //     if (intersection >= 90 || fact >= 0.9) {
    //         describe = "臭味相投";
    //     } else if (intersection >= 80 || fact >= 0.8) {
    //         describe = "心照神交";
    //     } else if (intersection >= 70 || fact >= 0.7) {
    //         describe = "相知恨晚";
    //     } else if (intersection >= 60 || fact >= 0.6) {
    //         describe = "志同道合";
    //     } else if (intersection >= 50 || fact >= 0.5) {
    //         describe = "同声相应";
    //     } else if (intersection >= 40 || fact >= 0.4) {
    //         describe = "不谋而合";
    //     } else if (intersection >= 30 || fact >= 0.3) {
    //         describe = "所见略同";
    //     } else if (intersection >= 20 || fact >= 0.2) {
    //         describe = "萍水相逢";
    //     } else if (intersection >= 10 || fact >= 0.1) {
    //         describe = "聊胜于无";
    //     } else if (intersection >= 1 || fact >= 0.01) {
    //         describe = "南辕北辙";
    //     } else {
    //         describe = "格格不入"; //断长续短
    //     }
    // }
    return describe ? describe + "的" + identity : identity;
}

let refreshing = false;
function refreshGameDivList() {
    if (refreshing) {
        return;
    }
    refreshing = true;

    var gameListElement = document.getElementsByClassName("_29H3o3m-GUmx6UfXhQaDAm");
    if (gameListElement && gameListElement.length) {
        for (var i = 0; i < gameListElement.length; ++i) {
            let gameDiv = gameListElement[i];
            let appid = getAppidFromGameDiv(gameListElement[i]);
            hideGameDiv(appid, gameDiv);
            markGameDiv(appid, gameDiv);
            priceGameDiv(appid, gameDiv);
        }
    }

    // let hisGameNum = hisAppidList ? hisAppidList.length : 0;
    // var gameDiv = document.querySelector("._29H3o3m-GUmx6UfXhQaDAm");
    // let i = 0;
    // while (gameDiv && ++i <= hisGameNum) {
    //     let appid = getAppidFromGameDiv(gameDiv);
    //     lastGameDivTop = hideGameDiv(appid, gameDiv, lastGameDivTop);
    //     markGameDiv(appid, gameDiv);
    //     gameDiv = gameDiv.nextElementSibling;
    // }

    refreshing = false;
}

function getVacAppidList() {
    return new Promise(function (resolve, reject) {
        load(url_vac_games, (res) => {
            let doc = domParser.parseFromString(res, "text/html");
            let searchResultRowEles = doc.getElementsByClassName("search_result_row");
            let vacAppidList = [];
            for (let i = 0; i < searchResultRowEles.length; i++) {
                let element = searchResultRowEles[i];
                let appid = getAppidFromGameDiv(element);
                if (appid && !vacAppidList.includes(appid)) {
                    vacAppidList.push(appid);
                }
            }
            resolve(vacAppidList);
        });
    });
}

async function privateGames(private, appidList, categorieIds, price) {
    let hisGameNum = hisAppidList ? hisAppidList.length : 0;
    let i = 0;
    let count = 0;
    let gameDiv = document.querySelector("._29H3o3m-GUmx6UfXhQaDAm");
    let sectionTabs = document.getElementsByClassName("sectionTab active");
    if (sectionTabs && sectionTabs.length) {
        for (let j = 0; j < sectionTabs.length; j++) {
            let sectionTab = sectionTabs[j];
            let span = sectionTab.querySelector("span");
            if (span && span.innerText) {
                let match = span.innerText.match(/\d+/);
                let numStr = match && match[0];
                hisGameNum = parseInt(numStr);
                break;
            }
        }
    }
    let gameNameList = "";
    const interval = setInterval(
        function () {
            if (gameDiv && ++i <= hisGameNum) {
                if (i % 5 == 0) gameDiv.scrollIntoView({ block: "center", inline: "nearest" });
                let btns = gameDiv.getElementsByClassName("_1pXbX5mBA7v__kVWHg0_Ja");
                let aEle = getAEleFromGameDiv(gameDiv);
                let appid = getAppidFromAEle(aEle);
                let appName = aEle.innerText;
                if (i % 50 == 0) console.log(i + "/" + hisGameNum);
                if (btns && btns.length > 0) {
                    if (private && btns.length == 1) {
                        if (
                            (!appidList && !categorieIds && price == null) ||
                            (appidList && appidList.includes(appid)) ||
                            (price != null &&
                                ((price == 0 && !appidPrice[appid]) ||
                                    (appidPrice[appid] && appidPrice[appid] <= price)))
                        ) {
                            privateGame(true, btns, ++count / 100 + 100);
                            gameNameList += appName + "\n";
                        } else if (categorieIds && categorieIds.length) {
                            getAppDetails(url_appdetails, appid).then((res) => {
                                let data;
                                if (
                                    res &&
                                    res[appid] &&
                                    (data = res[appid].data) &&
                                    data.categories
                                ) {
                                    for (let i = 0; i < data.categories.length; i++) {
                                        if (categorieIds.includes(data.categories[i].id)) {
                                            privateGame(true, btns, ++count / 100 + 100);
                                            gameNameList += appName + "\n";
                                            break;
                                        }
                                    }
                                }
                            });
                        }
                    } else if (!private && btns.length >= 2) {
                        privateGame(false, btns, ++count / 100 + 100);
                        gameNameList += appName + "\n";
                    }
                }

                let nextGameDiv = gameDiv.nextElementSibling;
                gameDiv = nextGameDiv ? nextGameDiv : i < hisGameNum ? gameDiv : null;
            } else {
                let privateResult = document.getElementById("privateResult");
                if (privateResult) {
                    privateResult.innerText =
                        "本次执行" +
                        (private ? "私密 " : "取消私密 ") +
                        count +
                        " 款游戏:\n" +
                        gameNameList;
                    privateResult.style.display = "block";

                    privateResult.previousElementSibling
                        ? privateResult.previousElementSibling.scrollIntoView({
                              block: "center",
                              inline: "nearest",
                          })
                        : privateResult.scrollIntoView({ block: "center", inline: "nearest" });
                }
                clearInterval(interval);
            }
        },
        private ? 10 : 1
    );
}

function getAppDetails(url, appids) {
    return new Promise(function (resolve, reject) {
        load(url + appids, (res) => {
            resolve(getJsonFromStr(res));
        });
    });
}

function privateGame(private, btns, timeout) {
    if (private) {
        btns[btns.length - 1].click();
        setTimeout(() => {
            let contextMenuItems = document.getElementsByClassName(
                "pFo3kQOzrl9qVLPXXGIMp contextMenuItem"
            );
            if (contextMenuItems && contextMenuItems.length >= 6) {
                contextMenuItems[5].click();
            }
        }, 5);
    } else {
        btns[btns.length - 2].click();
    }
    // return new Promise(function (resolve, reject) {
    //     setTimeout(() => {
    //         if (private) {
    //             btns[btns.length - 1].click();
    //             setTimeout(() => {
    //                 let contextMenuItems = document.getElementsByClassName(
    //                     "pFo3kQOzrl9qVLPXXGIMp contextMenuItem"
    //                 );
    //                 if (contextMenuItems && contextMenuItems.length >= 6) {
    //                     contextMenuItems[5].click();
    //                 }
    //                 resolve();
    //             }, 300);
    //         } else {
    //             btns[btns.length - 2].click();
    //             resolve();
    //         }
    //     }, timeout);
    // });
}

// function privateGame(private, gameDiv, btns, timeout) {
//     return new Promise(function (resolve, reject) {
//         gameDiv.scrollIntoView({ block: "end", inline: "nearest" });
//         if (private) {
//             setTimeout(function () {
//                 btns[btns.length - 1].click();
//                 setTimeout(() => {
//                     let contextMenuItems = document.getElementsByClassName(
//                         "pFo3kQOzrl9qVLPXXGIMp contextMenuItem"
//                     );
//                     if (contextMenuItems && contextMenuItems.length >= 6) {
//                         contextMenuItems[5].click();
//                     }
//                     resolve();
//                 }, 500);
//             }, timeout);
//         } else {
//             setTimeout(function () {
//                 btns[btns.length - 2].click();
//                 resolve();
//             }, timeout);
//         }
//     });
// }

function hideGameDiv(appid, gameDiv) {
    let display = gameDiv.style.display;
    if (
        (isHideOwn &&
            ((myAppidList && myAppidList.includes(appid)) ||
                (mySubAppidList && mySubAppidList.includes(appid)))) ||
        (isHideNotWish && myWishAppidList && !myWishAppidList.includes(appid))
    ) {
        display = "none";
    } else {
        display = "block";
    }
    if (display != gameDiv.style.display) {
        gameDiv.style.display = display;
    }
}

function addSectionTabListener() {
    let count = 0;
    const intervalId = setInterval(() => {
        if (count++ > retry) {
            clearInterval(intervalId);
            return;
        }
        let sectionTabs = document.getElementsByClassName("_1sHACvEQL-LRtUYan0JxdB");
        if (sectionTabs && sectionTabs.length > 0) {
            let curUrl = window.location.href;
            let regex = /games\/\?(\w|=|&)*?tab=(all|perfect|recent)/g;
            sectionTabs[0].addEventListener("click", function (event) {
                let targetUrl = event.target.baseURI ? event.target.baseURI : "";
                if (curUrl == targetUrl) {
                    return;
                }
                curUrl = targetUrl;
                if (regex.match(targetUrl)) {
                    refreshGameDivList();
                }
            });

            clearInterval(intervalId);
        }
    }, interval);
}

function addGameListObserver(interval) {
    let timeout;
    // let lastGameListElementLength = 0;
    window.addEventListener("scroll", () => {
        if (timeout) {
            clearTimeout(timeout);
        }
        timeout = setTimeout(() => {
            refreshGameDivList();
        }, interval);
    });
}

function markGameDiv(appid, gameDiv) {
    let color = gameDiv.style.backgroundColor;
    if (isMarkOwn && myAppidList && myAppidList.includes(appid)) {
        color = color_own;
    } else if (isMarkOwn && mySubAppidList && mySubAppidList.includes(appid)) {
        color = color_own_sub;
    } else if (isMarkWish && myWishAppidList && myWishAppidList.includes(appid)) {
        color = color_wish;
    }
    if (color != gameDiv.style.backgroundColor) {
        gameDiv.style.backgroundColor = color;
    }
}

function priceGameDiv(appid, gameDiv) {
    let price = appidPrice[appid];
    if (!price) {
        return;
    }
    let aEle = getAEleFromGameDiv(gameDiv);
    if (!aEle || aEle.innerText.includes("¥")) {
        return;
    }
    price = price / 100;
    aEle.innerText += "【¥" + price + "】";
    if (price >= price_high) {
        aEle.style.color = color_price_high;
    } else if (price >= price_middle) {
        aEle.style.color = color_price_middle;
    }
}

function load(url, callback) {
    try {
        return GM_xmlhttpRequest({
            method: "GET",
            url: url,
            onload: function (xhr) {
                // console.log(xhr);
                callback(xhr.responseText ? xhr.responseText : "");
            },
        });
    } catch (e) {
        // location.href = 'https://keylol.com';
        console.log(e);
    }
}

function getAppids(res, sort) {
    let appid;
    if (sort) {
        let appidAndplaytimeRegex =
            /appid("|\\"|&quot;):(\d+).*?playtime_forever("|\\"|&quot;):(\d+)/g;
        let obj = {};
        while ((appid = appidAndplaytimeRegex.exec(res))) {
            obj[appid[2]] = appid[4];
        }
        let sortedKeys = Object.keys(obj).sort((a, b) => obj[b] - obj[a]);
        return sortedKeys;
    } else {
        let appidRegex = /appid("|\\"|&quot;):(\d+)/g;
        let appidSet = new Set();
        // let appidList = [];
        while ((appid = appidRegex.exec(res))) {
            // appidList.push(appid[2]);
            appidSet.add(appid[2]);
        }
        return Array.from(appidSet);
    }
}
const appidRegex = /app\/(\d+)/;
function getAEleFromGameDiv(gameDiv) {
    return gameDiv && gameDiv.querySelector("._22awlPiAoaZjQMqxJhp-KP");
}

function getAppidFromAEle(aEle) {
    let href = aEle && aEle.getAttribute("href");
    let appid = appidRegex.exec(href ? href : "");
    return appid && parseInt(appid[1]);
}

function getAppidFromGameDiv(gameDiv) {
    let aEle = getAEleFromGameDiv(gameDiv);
    return getAppidFromAEle(aEle);
}

/**
 * 游戏库页面所有游戏列表
 * @param {*} document
 */
function getRgGames(dataProfileGameslist) {
    let rgGames = dataProfileGameslist && dataProfileGameslist.rgGames;
    return rgGames ? rgGames : [];
}

function getDataProfileGameslist(document) {
    let gameslist_config = document.getElementById("gameslist_config");
    if (gameslist_config) {
        // addGameListObserver(interval);
        let data_profile_gameslist = gameslist_config.getAttribute("data-profile-gameslist");
        return JSON.parse(data_profile_gameslist);
        // let rgGames = JSON.parse(data_profile_gameslist).rgGames;
        // return rgGames == null ? [] : rgGames;
        // // hisAppidList = getAppids(data_profile_gameslist);
    }
    return null;
}

function rgWishlistData(document) {
    const scriptElements = document.getElementsByTagName("script");
    for (const script of scriptElements) {
        // const scriptElement = document.querySelector("script");
        const scriptContent = script.textContent; // 获取 <script> 标签的内容
        if (!scriptContent) {
            continue;
        }
        const match = scriptContent.match(/var\s+g_rgWishlistData\s*=\s*(\[.+\])\s*;/); // 使用正则表达式匹配变量值
        const rgWishlistData = match && match[1]; // 提取变量值
        if (rgWishlistData) {
            return JSON.parse(rgWishlistData);
        }
    }
    return null;
}

function getJsonFromStr(str) {
    if (!str) {
        return {};
    }
    let s = str.indexOf("{");
    let e = str.lastIndexOf("}");
    if (s >= 0 && e > s) {
        return JSON.parse(str.substring(s, e + 1));
    }
}