您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
提供Steampy界面美化,功能增强,如库中已有游戏标记(支持家庭库及愿望单)、标记资料受限游戏等功能
// ==UserScript== // @name Better SteamPY // @namespace https://space.bilibili.com/93654843 // @version 20241208 // @description 提供Steampy界面美化,功能增强,如库中已有游戏标记(支持家庭库及愿望单)、标记资料受限游戏等功能 // @author FiNNiER // @match *://steampy.com/* // @icon https://steampy.com/img/logo.63413a4f.png // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @connect gitee.com // @connect api.steampowered.com // @connect store.steampowered.com // @run-at document-body // ==/UserScript== var Saves = { wishlist: [], ownedApps: [], familygameList: [], lastupdatetime: 0, }; var limitedApps = []; var noGameList = []; var noDlc = false; var noownedGames = false; var noRestrictedGames = false; (function () { 'use strict'; load(); observePageChanges(); })(); //读取个人库存及愿望单并储存 function getOwnAndWish() { return new Promise((resolve, reject) => { var wishlist = []; var ownedApps = []; GM_xmlhttpRequest({ method: 'GET', url: 'https://store.steampowered.com/dynamicstore/userdata/?t=' + Math.trunc(Date.now() / 1000), responseType: 'json', onload: function (response) { let data = JSON.parse(response.responseText); wishlist = data.rgWishlist; ownedApps = data.rgOwnedApps; let previousSaves = GM_getValue('Saves'); let newSave = { wishlist: wishlist, ownedApps: ownedApps, familygameList: previousSaves.familygameList, lastupdatetime: new Date().getTime(), }; GM_setValue('Saves', newSave); Saves = newSave; iview.Notice.success({ title: `Better Steampy`, desc: `已加载 ${ownedApps.length} 个库存游戏及DLC,${wishlist.length} 个愿望单游戏`, }); resolve(newSave); }, }); }); } //读取家庭库并储存 function getFamilyGame() { return new Promise((resolve, reject) => { var access_token; var family_groupid; var familygameList = []; GM_xmlhttpRequest({ method: 'GET', url: 'https://store.steampowered.com/pointssummary/ajaxgetasyncconfig', responseType: 'json', onload: function (response) { let data = JSON.parse(response.responseText); access_token = data.data.webapi_token; // access_token GM_xmlhttpRequest({ method: 'GET', url: `https://api.steampowered.com/IFamilyGroupsService/GetFamilyGroupForUser/v1/?access_token=${access_token}`, responseType: 'json', onload: function (response) { let data = JSON.parse(response.responseText); family_groupid = data.response.family_groupid; // family_groupid GM_xmlhttpRequest({ method: 'GET', url: `https://api.steampowered.com/IFamilyGroupsService/GetSharedLibraryApps/v1/?access_token=${access_token}&family_groupid=${family_groupid}&include_own=true`, responseType: 'json', onload: function (response) { let data = JSON.parse(response.responseText); data.response.apps.forEach((app) => { if (app.exclude_reason == 0) { familygameList.push(app.appid); } }); let previousSaves = GM_getValue('Saves'); let newSave = { wishlist: previousSaves.wishlist, ownedApps: previousSaves.ownedApps, familygameList: familygameList, lastupdatetime: new Date().getTime(), }; GM_setValue('Saves', newSave); Saves = newSave; iview.Notice.success({ title: `Better Steampy`, desc: `已加载 ${familygameList.length} 个家庭库游戏`, }); resolve(familygameList); }, }); }, }); }, }); }); } //获取受限游戏列表 function getLimitedGamesList() { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: 'https://gitee.com/Finnier/getSteamRestrictedGameLIst/raw/main/data/normalist.json', responseType: 'json', onload: function (response) { var data = JSON.parse(response.responseText); var limitedGames = data; GM_setValue('limitedApps', limitedGames); iview.Notice.success({ title: `Better Steampy`, desc: `已加载 ${limitedGames.length} 个非受限游戏`, }); resolve(limitedGames); }, }); }); } //获取非游戏列表 function getNogameList() { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: 'https://gitee.com/Finnier/getSteamAppListWithType/raw/main/data/Listwithnogame.json', responseType: 'json', onload: function (response) { var data = JSON.parse(response.responseText); var nogamelistdata = Object.keys(data).map(Number); GM_setValue('NoGameList', nogamelistdata); noGameList = nogamelistdata; iview.Notice.success({ title: `Better Steampy`, desc: `已加载 ${nogamelistdata.length} 个DLC及原声带`, }); resolve(nogamelistdata); }, }); }); } //初始化脚本配置菜单 function init() { const settings = document.createElement('div'); settings.innerHTML = ` <div id="settings" class="ml-20-rem"> <div class="withdraw" @click="modal=true, updateValues()">脚本设置</div> <Modal v-model="modal"> <br /> <Card> <template #title><h2>拥有状态标记</h2></template> <Alert type="warning" show-icon>暂时不支持捆绑包标记</Alert> <p> 上次更新于 <i-time :time="lastUpdateTime" :interval="1"></i-time> (每24小时执行一次自动更新) </p> <p>已加载 {{ownedApps}} 个库存游戏及DLC</p> <p>已加载 {{wishlist}} 个愿望单游戏</p> <p>已加载 {{familygameList}} 个家庭库游戏</p> <div> 是否加入了家庭组:<i-Switch v-model="isInFamilyGroup" @on-change="isInFamilyGroup_change" /> </div> <br /> <button-group size="large" shape="circle"> <i-Button @click="reloadSaves" :loading="refershSaves_loading" >重载存档</i-Button > <i-Button @click="clearSaves">清除存档</i-Button> </button-group> </Card> <Card> <template #title><h2>个人资料功能受限标记</h2></template> <Alert show-icon >数据来源于https://github.com/F1NN1ER/getSteamRestrictedGameLIst</Alert > <Alert show-icon>数据每日更新,可能尚有部分未及时标记</Alert> <div> 是否启用受限游戏标注:<i-Switch v-model="checkIsProfileFeatureLimited" @on-change="checkIsProfileFeatureLimited_change" /> </div> <p>目前共加载{{limitedApps}}个非受限游戏(跟随拥有状态自动更新)</p> <i-Button @click="reloadLimitedSaves" :loading="reloadLimitedSaves_loading" >刷新</i-Button > </Card> <Card> <template #title><h2>标记颜色设置</h2></template> <div> 已拥有 <Color-Picker v-model="ownedAppsColor" size="small" :colors="defaultcolors" @on-change="ownedAppsColor_change" /> </div> <div> 在愿望单中 <Color-Picker v-model="wishlistColor" size="small" :colors="defaultcolors" @on-change="wishlistColor_change" /> </div> <div> 在家庭库中 <Color-Picker v-model="familygameColor" size="small" :colors="defaultcolors" @on-change="familygameColor_change" /> </div> <div> 未拥有 <Color-Picker v-model="unownedColor" size="small" :colors="defaultcolors" @on-change="unownedColor_change" /> </div> </Card> <Card> <template #title><h2>网页优化</h2></template> <div> 是否关闭网页右下方推广侧栏:<i-Switch v-model="isSuspensionOff" @on-change="isSuspensionOff_change" /> </div> </Card> </Modal> </div> `; const filter = document.createElement('div'); filter.innerHTML = ` <div id="filter" class="ml-20-rem"> <Space direction="vertical" size="large"> <div id="filter"> <Checkbox-Group v-model="filter" @on-change="filterChange"> <Checkbox label="noOwnedGames" border>不显示已拥有游戏</Checkbox> <Checkbox label="noRestrictedGames" border >不显示资料受限游戏</Checkbox> <Checkbox label="noDlc" border>不显示DLC及原声带</Checkbox> </Checkbox-Group> </Space> `; const targetElement = document.querySelector('.balanceTitle > div'); targetElement.appendChild(settings); targetElement.appendChild(filter); new Vue({ el: '#settings', data() { return { reloadLimitedSaves_loading: false, refershSaves_loading: false, modal: false, lastUpdateTime: Saves.lastupdatetime, ownedApps: Saves.ownedApps.length, wishlist: Saves.wishlist.length, familygameList: Saves.familygameList.length, limitedApps: limitedApps.length, isInFamilyGroup: JSON.parse(localStorage.getItem('isInfamily')), checkIsProfileFeatureLimited: JSON.parse( localStorage.getItem('IsProfileFeatureLimited') ), isSuspensionOff: JSON.parse(localStorage.getItem('isSuspensionOff')), ownedAppsColor: localStorage.getItem('ownedColor'), wishlistColor: localStorage.getItem('wishlistColor'), familygameColor: localStorage.getItem('familygameColor'), unownedColor: localStorage.getItem('unownedColor'), defaultcolors: ['#0c8918', '#177cb0', '#ff8936', '#ff2e63'], }; }, methods: { updateValues() { this.ownedApps = Saves.ownedApps.length; this.wishlist = Saves.wishlist.length; this.familygameList = Saves.familygameList.length; this.limitedApps = limitedApps.length; this.lastUpdateTime = Saves.lastupdatetime; }, isInFamilyGroup_change(status) { if (status) { localStorage.setItem('isInfamily', JSON.stringify(true)); } else { localStorage.removeItem('isInfamily'); } }, checkIsProfileFeatureLimited_change(status) { if (status) { localStorage.setItem('IsProfileFeatureLimited', JSON.stringify(true)); Saves = GM_getValue('Saves'); const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((element) => { cdkeyGameChecker(element); }); } else { localStorage.removeItem('IsProfileFeatureLimited'); const elements = document.querySelectorAll('.ProfileFeaturesLimited'); elements.forEach((element) => { element.parentNode.removeChild(element); }); } }, isSuspensionOff_change(status) { if (status) { localStorage.setItem('isSuspensionOff', JSON.stringify(true)); GM_addStyle('.suspension{display:none}'); } else { GM_addStyle('.suspension{display:block}'); localStorage.removeItem('isSuspensionOff'); } }, ownedAppsColor_change(color) { ownedColor = color; localStorage.setItem('ownedColor', color); const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((element) => { cdkeyGameChecker(element); }); }, wishlistColor_change(color) { wishlistColor = color; localStorage.setItem('wishlistColor', color); const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((element) => { cdkeyGameChecker(element); }); }, familygameColor_change(color) { familygameColor = color; localStorage.setItem('familygameColor', color); const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((element) => { cdkeyGameChecker(element); }); }, unownedColor_change(color) { unownedColor = color; localStorage.setItem('unownedColor', color); const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((element) => { cdkeyGameChecker(element); }); }, async reloadSaves() { this.$Notice.info({ title: '正在重载存档', }); this.refershSaves_loading = true; await Promise.all([ getOwnAndWish(), this.isInFamilyGroup ? getFamilyGame() : Promise.resolve(), ]); Saves = GM_getValue('Saves'); const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((element) => { cdkeyGameChecker(element); }); this.updateValues(); this.refershSaves_loading = false; this.$Notice.success({ title: '重载完毕', }); }, async reloadLimitedSaves() { this.$Notice.info({ title: '正在加载受限游戏列表', }); this.reloadLimitedSaves_loading = true; await getLimitedGamesList(); await getNogameList(); limitedApps = GM_getValue('limitedApps'); this.updateValues(); this.reloadLimitedSaves_loading = false; this.$Notice.success({ title: '加载完毕', }); }, clearSaves() { this.$Notice.info({ title: '存档已清除', }); let nullSaves = { wishlist: [], ownedApps: [], familygameList: [], lastupdatetime: 0, }; Saves = nullSaves; GM_setValue('Saves', nullSaves); this.updateValues(); }, }, }); new Vue({ el: '#filter', data() { return { filter: [], }; }, methods: { filterChange() { noownedGames = this.filter.includes('noOwnedGames'); noRestrictedGames = this.filter.includes('noRestrictedGames'); noDlc = this.filter.includes('noDlc'); const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((element) => { cdkeyGameChecker(element); }); }, }, }); if (localStorage.getItem('isSuspensionOff') === 'true') { GM_addStyle('.suspension{display:none}'); } } //游戏状态标记-CDKEY function cdkeyGameChecker(element) { const isAppOwned = (appId) => Saves.ownedApps.includes(appId); const isAppinwishlist = (appId) => Saves.wishlist.includes(appId); const isAppShared = (appId) => Saves.familygameList.includes(appId); const isNotLimited = (appId) => !limitedApps.includes(appId); const isDLC = (appId) => noGameList.includes(appId); const getAppId = (url) => (url.match(/\/apps\/(\d+)\//) || [])[1] || null; const getBundleId = (url) =>(url.match(/\/bundles\/(\d+)\//) || [])[1] || null; const appId = Number(getAppId(element.getAttribute('data-src'))); const gameNameElement = element .closest('.gameblock') .querySelector('.gameName'); if (appId != 0) { element.parentElement.parentElement.style.display = 'block'; if (noDlc) { if (isDLC(appId)) { element.parentElement.parentElement.style.display = 'none'; } } if (isAppOwned(appId)) { if (noownedGames) { element.parentElement.parentElement.style.display = 'none'; } else { gameNameElement.style.color = ownedColor; } } else if (isAppShared(appId)) { gameNameElement.style.color = familygameColor; } else if (isAppinwishlist(appId)) { gameNameElement.style.color = wishlistColor; } else { gameNameElement.style.color = unownedColor; } if (localStorage.getItem('IsProfileFeatureLimited')) { const existingDiscountDiv = element.parentElement.querySelector( '.ProfileFeaturesLimited' ); if (existingDiscountDiv) { existingDiscountDiv.remove(); } if (isNotLimited(appId)) { if (noRestrictedGames) { element.parentElement.parentElement.style.display = 'none'; } else { const discountDiv = document.createElement('div'); discountDiv.className = 'ProfileFeaturesLimited'; discountDiv.textContent = '资料受限'; element.parentElement.appendChild(discountDiv); } } } } } //加载存档 function load() { var previousSave = GM_getValue('Saves'); if (previousSave !== undefined) { Saves = GM_getValue('Saves'); } else { GM_setValue('Saves', Saves); } var previousLimitedApps = GM_getValue('limitedApps'); if (previousLimitedApps !== undefined) { limitedApps = GM_getValue('limitedApps'); } else { getLimitedGamesList(); } var previousNoGameList = GM_getValue('NoGameList'); if (previousNoGameList !== undefined) { noGameList = GM_getValue('NoGameList'); } else { getNogameList(); } //自动更新 if (new Date().getTime() - Saves.lastupdatetime > 86400000) { iview.Notice.info({ title: '存档自动更新中', }); getOwnAndWish(); if (JSON.parse(localStorage.getItem('isInfamily'))) { getFamilyGame(); } getLimitedGamesList(); getNogameList(); } } //监听页面变化 function observePageChanges() { const config = { childList: true, subtree: true, attributes: true, attributeFilter: ['data-src'], }; let hasExecuted = false; const callback = function (mutationsList, observer) { for (let mutation of mutationsList) { if ( mutation.type === 'attributes' && mutation.attributeName === 'data-src' ) { const targetElement = mutation.target; if (targetElement.classList.contains('cdkGameIcon')) { cdkeyGameChecker(targetElement); } } if (!hasExecuted && mutation.type === 'childList') { const balanceTitleElement = document.querySelector( '.balanceTitle > div' ); if (balanceTitleElement) { init(); hasExecuted = true; } } } }; const observer = new MutationObserver(callback); observer.observe(document.body, config); } //CSS样式 const style = document.createElement('style'); style.innerHTML = ` .ProfileFeaturesLimited { width: .65rem; height: .3rem; background: #ed4014; position: absolute; top: 0; color: #fff; text-align: center; line-height: .3rem; font-size: .12rem; } `; document.head.appendChild(style); //默认颜色 if (!localStorage.getItem('ownedColor')) { localStorage.setItem('ownedColor', '#0c8918'); localStorage.setItem('wishlistColor', '#177cb0'); localStorage.setItem('familygameColor', '#ff8936'); localStorage.setItem('unownedColor', '#ff2e63'); } var ownedColor = localStorage.getItem('ownedColor'); var wishlistColor = localStorage.getItem('wishlistColor'); var familygameColor = localStorage.getItem('familygameColor'); var unownedColor = localStorage.getItem('unownedColor'); //Todo list: //夜间模式 //侧栏收放 //中键快捷控制标签页 //左右翻页