Steam 家庭库已有游戏标记

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

目前为 2024-04-07 提交的版本。查看 最新版本

// ==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;
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址