Greasy Fork

Greasy Fork is available in English.

Steam 家庭库已有游戏标记

能够自动扫描你的家庭库库存,并在Steam游戏页面标记,并支持一键安装游戏

当前为 2024-04-07 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Steam 家庭库已有游戏标记
// @namespace    http://tampermonkey.net/
// @version      2024-04-08
// @description  能够自动扫描你的家庭库库存,并在Steam游戏页面标记,并支持一键安装游戏
// @author       Cliencer Goge
// @match        https://store.steampowered.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=steampowered.com
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_xmlhttpRequest
// @license      GPLv3
// ==/UserScript==

var dialog,appid,observer,isupdate
var saves = readstorage()
//console.log(saves)
const url = window.location.pathname;
var access_token,steamid
if(g_AccountID != 0){
    access_token = JSON.parse(application_config.getAttribute("data-store_user_config")).webapi_token
    steamid = JSON.parse(application_config.getAttribute("data-userinfo")).steamid

}
(function() {
    'use strict';
    init()
    if(g_AccountID == 0){return;}


    if(url=='/account/familymanagement' && saves.isStartDump){
        observer_1();
    }else{
        if(saves.settings.isAutoScan && g_ServerTime-saves.lastupDateTime>86400){
            scan(false)
        }
        if(!isupdate && g_ServerTime-saves.lastupDateTime>604800){
            let innerText
            if(saves.familyGameList.GameList.length == 0){
                innerText="您似乎没有家庭库的游戏记录,是否现在扫描家庭库游戏并记录呢?"
            }else{
                innerText="您已经超过1周没有更新家庭库的游戏列表了,是否现在去扫描?"
            }
            ShowConfirmDialog('脚本提示',innerText,'扫描家庭库','取消').done(()=>{scan(true)}).fail(()=>{
                ShowAlertDialog('脚本提示','如果需要手动扫描,可以在Steam主页右上角进入进行扫描','好的')
            })
        }
        var search_suggestion = document.getElementById('search_suggestion_contents')
        var observer_search = new MutationObserver((mutations, obs) => {
            mutations.forEach(function(mutation) {
                if (mutation.addedNodes && mutation.addedNodes.length > 0) {

                    mutation.addedNodes.forEach(function(node) {
                        // 确保是元素节点
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            // 检查新节点是否有指定的类
                            if (node.classList.contains('match_app')) {
                                addflag(node)
                            }
                        }
                    });
                }
            })
        });

        observer_search.observe(search_suggestion, {childList: true, subtree: true});




    }
    if(url == "/"){
        observer_3()
        observer = new MutationObserver((mutations, obs) => {
            mutations.forEach(function(mutation) {
                if (mutation.addedNodes && mutation.addedNodes.length > 0) {
                    mutation.addedNodes.forEach(function(node) {
                        // 确保是元素节点
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            if(node.classList.contains('live_streams_ctn')){return;}

                            node.querySelectorAll("div").forEach((node)=>{
                                addflag(node)
                            })
                            node.querySelectorAll("a").forEach((node)=>{
                                if(node.classList.contains('screenshot')){return;}
                                if(node.querySelector('div.broadcast_live_stream_icon')){return;}
                                addflag(node)
                            })

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

    }
    if(url.startsWith('/app/')&&g_AccountID != 0){
        //addBanner(document.querySelector('div.block.game_media_and_summary_ctn'))
        observer_2();
    }
    if(url.startsWith('/search/')&&g_AccountID != 0){
        observer_4()
        var search_results = document.getElementById('search_results')
        observer = new MutationObserver((mutations, obs) => {
            mutations.forEach(function(mutation) {
                if (mutation.addedNodes && mutation.addedNodes.length > 0) {

                    mutation.addedNodes.forEach(function(node) {
                        // 确保是元素节点
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            console.log(node)
                            if (node.classList.contains('search_result_row') && node.classList.contains('ds_collapse_flag') && !node.classList.contains('ds_owned')) {
                                addflag(node,"clear: left;")
                            }else{
                                let lists = node.querySelectorAll("a.search_result_row.ds_collapse_flag")
                                lists.forEach(function(bar){
                                    addflag(bar,"clear: left;")
                                })
                            }
                        }
                    });
                }
            })
        });

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


    }

    function init(){

        let setting_btn = document.createElement('span');
        setting_btn.id = "setting_btn"
        setting_btn.style = "position:relative;background:linear-gradient(to right, rgb(6 207 199 / 60%) 0%, rgb(33 105 106 / 60%) 100%)"
        setting_btn.innerHTML = `<a href="javascript:void(0)" style = "color:#06cfb5">家庭游戏标记 脚本设置</a></span>`
        setting_btn.onclick = btnonclick
        plug();





        function plug(){
            let headding = document.getElementById('global_action_menu')
            if(headding){
                headding.insertBefore(setting_btn, headding.firstChild);
            }else{
                setTimeout(plug,200)
            }
        }
        function btnonclick(){
            let innerHTML = `<div id="family_tool_options">
                 <div style="margin-bottom:6px;">目前你的家庭【${saves.familyInfo.family_name}】一共记录了 ${saves.familyGameList.GameList.length} 个共享游戏。</div>
                 <div style="margin-bottom:6px;">上次扫描时间: ${timestampToTime(saves.lastupDateTime)}</div>
                 <div style="margin-bottom:6px;"><input class="price_option_input" style="background-color: black;color: white;border: transparent;" type="checkbox" id="isAutoScan" ${saves.settings.isAutoScan ? 'checked=""':''}>&nbsp;&nbsp;每隔24小时自动后台扫描并缓存&nbsp;</div>
            </div>
            `
            ShowConfirmDialog(`脚本设置`,innerHTML,'扫描家庭库','取消','清空库记录',{strSubTitle:'点击外部空白处即可保存退出',bExplicitDismissalOnly:false}).done(function(arg){
                if(arg == 'SECONDARY'){
                    ShowConfirmDialog('再次确认','你即将清空当前保存的家庭库列表,该行为无法撤销!','好的','算了').done(() =>{
                        saves = readstorage(true)
                        savestorage()
                        ShowAlertDialog('完成','已经清除所有的缓存','好的')
                    })
                }else{
                    scan(true)
                }
            }).fail(()=>{
                saves.settings.isAutoScan = isAutoScan.checked
                savestorage()
            })


        }




    }

    function observer_4(){
        let block = document.getElementById('search_result_container')
        if(block){
            let lists = block.querySelectorAll("a.search_result_row.ds_collapse_flag")
            lists.forEach(function(bar){
                addflag(bar,"clear: left;")
            })

        }else{
            setTimeout(observer_4,200)
        }

    }
    function observer_3(){
        let block = document.querySelector('div.home_tabs_content')
        if(block){
            let lists = block.querySelectorAll("a.tab_item")
            lists.forEach(function(bar){
                addflag(bar,"clear: both;")
            })

            block = document.querySelector('div.carousel_container.maincap')
            lists = block.querySelectorAll("a.store_main_capsule")
            lists.forEach(function(bar){
                addflag(bar)
            })

            block = document.querySelector('div.carousel_container.spotlight')
            lists = block.querySelectorAll("div.home_area_spotlight")
            lists.forEach(function(bar){
                addflag(bar)
            })
            lists = block.querySelectorAll("a.store_capsule")
            lists.forEach(function(bar){
                addflag(bar)
            })


            block = document.getElementById('module_deep_dive')
            lists = block.querySelectorAll("a.store_capsule")
            lists.forEach(function(bar){
                addflag(bar)
            })

            block = document.getElementById('module_recommender')
            lists = block.querySelectorAll("a.store_capsule")
            lists.forEach(function(bar){
                addflag(bar)
            })


            block = document.getElementById('recommended_creators_carousel')
            lists = block.querySelectorAll("a.store_capsule")
            lists.forEach(function(bar){
                addflag(bar)
            })

            block = document.querySelector('div.specials_under10_content')
            lists = block.querySelectorAll("a.store_capsule")
            lists.forEach(function(bar){
                addflag(bar)
            })

            block = document.querySelector('div.marketingmessage_area')
            lists = block.querySelectorAll("a.home_marketing_message")
            lists.forEach(function(bar){
                addflag(bar)
            })

        }else{
            setTimeout(observer_3,200)
        }
    }

    function observer_2(){
        let block = document.querySelector('div.block.game_media_and_summary_ctn')
        if(block){
            appid = Number(url.split('/')[2])
            if(saves.familyGameList.GameList.includes(appid)){
                addBanner(block,appid)
            }
        }else{
            setTimeout(observer_2,200)
        }
    }
    function addflag(node,insertBeforeStyle){
        if(node.querySelector("div.ds_owned_flag")) return;

        let thisappid = node.getAttribute('data-ds-appid')
        let thisurl = node.getAttribute('href')

        if(thisappid && (thisurl == null || thisurl.startsWith('https://store.steampowered.com/app/')) && saves.familyGameList.GameList.includes(Number(thisappid))){
            if(url.startsWith('/app/')){
                node.classList.add('ds_owned');
            }
            node.classList.add('ds_flagged');
            node.classList.remove('ds_wishlist')
            var flag = document.createElement('div');
            flag.className = "ds_flag ds_owned_flag"
            flag.innerHTML = '在家庭库中&nbsp;&nbsp;'
            flag.style = "background:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAKCAYAAABi8KSDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUNDNzBFNTUyMUM0MTFFNDk1REVFODRBNUU5RjA2MUYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUNDNzBFNTYyMUM0MTFFNDk1REVFODRBNUU5RjA2MUYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5Q0M3MEU1MzIxQzQxMUU0OTVERUU4NEE1RTlGMDYxRiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5Q0M3MEU1NDIxQzQxMUU0OTVERUU4NEE1RTlGMDYxRiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv3vUKAAAAAlSURBVHjaYvz//z8DsYARpFhISAivjnfv3jGSp3jUGeQ4AyDAADZHNe2nyOBrAAAAAElFTkSuQmCC') no-repeat 4px 4px #06cfbe"
            if(insertBeforeStyle){
                node.insertBefore(flag, node.querySelector(`[style*="${insertBeforeStyle}"]`).nextSibling);
            }else{
                node.appendChild(flag);
            }
            node.querySelectorAll("div.ds_flag.ds_wishlist_flag").forEach((wishlist_flag)=>{wishlist_flag.remove()})

        }

    }
    function addBanner(block,appid){
        let appname = appHubAppName.innerText
        let owned = false
        let thisgameInfo = saves.familyGameList.GameInfo[appid]
        if(block.querySelector('div.game_area_already_owned.page_content')|| thisgameInfo.owners.includes(steamid)){
            owned = true
        }
        if(owned == false){

            var headplug = document.createElement('div');
            var targetElement = block.querySelector('div.queue_overflow_ctn');
            headplug.style = "background:linear-gradient(to right, rgb(6 207 199 / 60%) 0%, rgb(33 105 106 / 60%) 100%);color:#06cfb5"
            headplug.className = "game_area_already_owned page_content"
            headplug.innerHTML =`<div class="game_area_already_owned_ctn" >
				                   <div class="ds_owned_flag ds_flag" style="background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAKCAYAAABi8KSDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUNDNzBFNTUyMUM0MTFFNDk1REVFODRBNUU5RjA2MUYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUNDNzBFNTYyMUM0MTFFNDk1REVFODRBNUU5RjA2MUYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5Q0M3MEU1MzIxQzQxMUU0OTVERUU4NEE1RTlGMDYxRiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5Q0M3MEU1NDIxQzQxMUU0OTVERUU4NEE1RTlGMDYxRiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv3vUKAAAAAlSURBVHjaYvz//z8DsYARpFhISAivjnfv3jGSp3jUGeQ4AyDAADZHNe2nyOBrAAAAAElFTkSuQmCC) no-repeat 4px 4px #06cfbe">在家庭库中&nbsp;&nbsp;</div>
				                   <div class="already_in_library" >您的 Steam 家庭库中已有《${appname}》</div>
		                     </div>`

            targetElement.parentNode.insertBefore(headplug, targetElement.nextSibling);


            var endplug = document.createElement('div');
            targetElement = block.querySelector('div.purchase_options_content');
            endplug.className = "game_area_play_stats"
            endplug.innerHTML = `<div class="already_owned_actions">
								       <div class="game_area_already_owned_btn">
									         <a class="btnv6_lightblue_blue btnv6_border_2px btn_medium" href="https://store.steampowered.com/about/?snr=1_5_9__owned-game">
										        <span>安装 Steam</span>
									         </a>
								       </div>
									   <div class="game_area_already_owned_btn">
										     <a class="btnv6_lightblue_blue btnv6_border_2px btn_medium" href="steam://launch/${appid}/Dialog">
										        <span>马上开玩</span>
									         </a>
								       </div>
                                       <div id ="see_family_benefactor" style="position: relative; display: inline-block;" data-tooltip-text="您有 ${thisgameInfo.owners.length} 个家庭组成员拥有此游戏"><div class="game_area_already_owned_btn">
								             <a class="btnv6_lightblue_blue btnv6_border_2px btn_medium">
										        <span>查看贡献者</span>
									         </a>
                                             <div style="position: absolute; top: -5px; right: -8px; background-color: red; color: white; border-radius: 50%; width: 20px; height: 20px; display: flex; justify-content: center; align-items: center;">
                                                 <span style="font-size: 14px;">${thisgameInfo.owners.length}</span>
                                             </div>
                                       </div></div>

							     </div>
					             <div style="clear:left;"></div>`
            targetElement.parentNode.insertBefore(endplug, targetElement);
            (function observer_1(){
                let btn = document.getElementById('see_family_benefactor')
                if(btn){
                    let innerHTML = `<div style='margin-bottom:6px;'>您有 ${thisgameInfo.owners.length} 个家庭组成员拥有此游戏:</div>`
                    thisgameInfo.owners.forEach((steamid)=>{
                           innerHTML+= `<div style='margin-bottom:6px;'>${saves.familyInfo.steamIdtoName[steamid]}</div>`
                    })
                    innerHTML+= `<div style='margin-bottom:6px;'>--------------------------------------------</div>
                    <div style='margin-bottom:6px;'>该游戏最早由【${saves.familyInfo.steamIdtoName[thisgameInfo.owners[0]]}】于 ${timestampToTime(thisgameInfo.time)} 购入。</div>`
                    btn.onclick = function(){
                           ShowAlertDialog(`【${saves.familyInfo.family_name}】游戏贡献者`,innerHTML,'好的')
                    }
                }else{
                    setTimeout(observer_1,200)
                }
            })();

        }

    }

    function getGameAppid(element){
        return Number(element.firstChild.firstChild.getAttribute('src').split('/')[5])
    }
    function getGameCounts(containGames_panel){
        return Number(containGames_panel.querySelector('div.LP9H7bBiPB8N8jFzCQumL').lastChild.innerText.match(/\d*/)[0])
    }
})();

function scan(isdialog){
    ShowAlertDialog('提示','即将开始扫描,请确认已加入一个有效的家庭组,否则脚本可能会出错,扫描期间不要关闭浏览器,耐心等待!','好的,开始扫描').done(()=>{
        if(isdialog){
            dialog = ShowBlockingWaitDialog('正在扫描家庭组库存...')
        }
        getfamilyInfo(access_token).then((returnjson) => {
            saves.familyInfo = returnjson
            savestorage()
            getfamilyGameList(access_token,saves.familyInfo.family_groupid).then((returnjson) => {
                saves.familyGameList = returnjson
                saves.lastupDateTime = g_ServerTime
                savestorage()
                if(isdialog) dialog.Dismiss()
                ShowAlertDialog('完成',`已将${saves.familyGameList.GameList.length}个家庭库游戏记录到本地缓存。`,'好的')
            })
        })
    })
}


function getfamilyGameList(access_token,family_groupid){
    return new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest();
        var json = null
        var returnjson = {"GameList":[],"GameInfo":{}}
        xhr.open("GET", `https://api.steampowered.com/IFamilyGroupsService/GetSharedLibraryApps/v1/?access_token=${access_token}&family_groupid=${family_groupid}&include_own=true&include_excluded=false&include_non_games=false`, true);
        xhr.onload = function() {
            if (xhr.status >= 200 && xhr.status < 300) {
                json = JSON.parse(xhr.responseText).response;
                if(json){
                    json.apps.forEach((app)=>{
                        if(app.exclude_reason == 0){
                            returnjson.GameList.push(app.appid)
                            returnjson.GameInfo[app.appid] = {"owners":app.owner_steamids,
                                                              "time":app.rt_time_acquired}
                        }
                    })
                    resolve(returnjson)
                }
            } else {
                console.error("请求出错:", xhr.statusText);
            }
        };
        xhr.send();
    });
}

function getfamilyInfo(access_token){
    return new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest();
        var json
        xhr.open("GET", `https://api.steampowered.com/IFamilyGroupsService/GetFamilyGroupForUser/v1/?access_token=${access_token}&include_family_group_response=true`, true);
        xhr.onload = function() {
            if (xhr.status >= 200 && xhr.status < 300) {
                json = JSON.parse(xhr.responseText).response;
                if(json){
                    var returnjson = {
                        "family_groupid":json.family_groupid,
                        "family_name":json.family_group.name,
                        "family_member":json.family_group.members,
                        "steamIdtoName":{}
                    }

                    getUserNameBySteamId(access_token,json.family_group.members).then((ret)=>{
                        returnjson.family_member = ret.family_member
                        returnjson.steamIdtoName = ret.steamIdtoName
                        resolve(returnjson)
                    })
                }

            } else {
                reject("请求出错:", xhr.statusText);
            }
        };
        xhr.send();


    })
}


function getUserNameBySteamId(access_token,family_member) {
    return new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest();
        var json = null
        var steamIdtoName ={}
        var url = `https://api.steampowered.com/IPlayerService/GetPlayerLinkDetails/v1/?access_token=${access_token}`
        let i = 0
        family_member.forEach((member)=>{
            url+=`&steamids[${i}]=${member.steamid}`
            i++
        })

        xhr.open("GET",url , true);
        xhr.onload = function() {
            if (xhr.status >= 200 && xhr.status < 300) {
                json = JSON.parse(xhr.responseText).response;
                if(json){
                    json.accounts.forEach((user)=>{
                        let i = 0
                        family_member.forEach((member)=>{
                            if(member.steamid == user.public_data.steamid){
                                family_member[i].userName = user.public_data.persona_name
                            }
                            i++
                        })
                        steamIdtoName[user.public_data.steamid]=user.public_data.persona_name
                    })
                    resolve({family_member:family_member,steamIdtoName:steamIdtoName})
                }
            } else {
                console.error("请求出错:", xhr.statusText);
            }
        };
        xhr.send();
    });
}



function readstorage(isnew){
    var saves = GM_getValue('saves')
    let newsaves = {
        version : 20240407,
        familyGameList:{"GameList":[],"GameInfo":{}},
        familyInfo:{"family_groupid":null,
                    "family_name":null,
                    "family_member":{},
                    "steamIdtoName":{}},
        lastupDateTime:0,
        settings:{isAutoScan:true}
    }
    if(isnew) return newsaves
    if(saves){
        if(saves.version == newsaves.version){
            return saves
        }else{
            isupdate=true
            ShowConfirmDialog('脚本提示','脚本缓存列表结构升级,缓存的家庭库列表需要重新扫描!','扫描家庭库','取消').done(()=>{scan(true)}).fail(()=>{
                ShowAlertDialog('脚本提示','如果需要手动扫描,可以在Steam主页右上角进入进行扫描','好的')
            })
            //newsaves.familyGameList.GameList = saves.familyGameList
            //newsaves.lastupDateTime = saves.lastupDateTime//存档结构升级,兼容旧版
        }
    }
    return newsaves
}
function savestorage(){
    GM_setValue('saves',saves)
}
function timestampToTime(timestamp) {
    if(timestamp == 0){return '无记录'}
    timestamp = timestamp ? timestamp : null;
    timestamp *= 1000
    let date = new Date(timestamp);//时间戳为10位需*1000,时间戳为13位的话不需乘1000
    let Y = date.getFullYear() + '-';
    let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
    let D = (date.getDate() < 10 ? '0' + date.getDate() : date.getDate()) + ' ';
    let h = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':';
    let m = (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()) + ':';
    let s = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds();
    return Y + M + D + h + m + s;
}