提供Steampy界面美化,功能增强,如库中已有游戏标记(支持家庭库及愿望单)等功能
当前为
// ==UserScript==
// @name Better SteamPY
// @namespace https://space.bilibili.com/93654843
// @version 2024-08-19
// @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 api.steampowered.com
// @connect store.steampowered.com
// @run-at document-body
// ==/UserScript==
// ==颜色配置==
const ownedColor = '#0c8918'; // 已拥有
const wishlistColor = '#177cb0'; // 愿望单中
const familygameColor = '#ff8936'; // 家庭库中
const unownedColor = '#ff2e63'; // 未拥有
// ==颜色配置==
var Saves = {
wishlist: [],
ownedApps: [],
familygameList: [],
};
(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) {
try {
let data = JSON.parse(response.responseText);
wishlist = data.rgWishlist;
ownedApps = data.rgOwnedApps;
let previousSaves = GM_getValue('Saves', {
wishlist: [],
ownedApps: [],
familygameList: [],
});
let newSave = {
wishlist: wishlist,
ownedApps: ownedApps,
familygameList: previousSaves.familygameList,
};
GM_setValue('Saves', newSave);
resolve(newSave);
} catch (error) {
reject(error);
}
},
onerror: function (error) {
reject(error);
},
});
});
}
//读取家庭库并储存
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) {
try {
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) {
try {
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) {
try {
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', {
wishlist: [],
ownedApps: [],
familygameList: [],
});
let newSave = {
wishlist: previousSaves.wishlist,
ownedApps: previousSaves.ownedApps,
familygameList: familygameList,
};
GM_setValue('Saves', newSave);
resolve(familygameList);
} catch (error) {
reject(error);
}
},
onerror: function (error) {
reject(error);
},
});
} catch (error) {
reject(error);
}
},
onerror: function (error) {
reject(error);
},
});
} catch (error) {
reject(error);
}
},
onerror: function (error) {
reject(error);
},
});
});
}
//初始化脚本配置菜单
function init() {
const settings = document.createElement('div');
settings.innerHTML = `
<div class="ml-20-rem">
<div class="withdraw" id="settings">脚本设定</div>
</div>
<div id="popup-window" class="popup-window">
<div class="popup-content">
<span class="close-btn" id="close-popup">×</span>
<h2>标记设置</h2>
<div id="loading" style="display: none;">加载中...</div>
<p id="ownedAppsCount">已加载${
GM_getValue('Saves').ownedApps.length
} 个库存游戏及DLC</p>
<p id="wishListCount">已加载${
GM_getValue('Saves').wishlist.length
} 个愿望单游戏</p>
<p id="familyGameCount">已加载${
GM_getValue('Saves').familygameList.length
} 个家庭库游戏</p>
<label for="family-library">是否加入了家庭库:</label>
<input type="checkbox" id="family-library" name="family-library">
<div class="button-container">
<button type="button" id="refresh-saves" class="ivu-btn ivu-btn-primary ivu-btn-small">刷新存档</button>
<button type="button" id="clear-saves" class="ivu-btn ivu-btn-primary ivu-btn-small">清除存档</button>
<p>注:暂不支持捆绑包类物品识别</p>
</div>
<hr>
<h2>美化设置</h2>
<label for="isSuspensionOff">是否关闭网页右下方推广侧栏:</label>
<input type="checkbox" id="isSuspensionOff" name="isSuspensionOff">
<div class="button-container">
</div>
</div>
</div>
<div id="overlay" class="overlay"></div>
`;
const targetElement = document.querySelector('.balanceTitle > div');
targetElement.appendChild(settings);
const popup = document.getElementById('popup-window');
const overlay = document.getElementById('overlay');
document.getElementById('settings').addEventListener('click', function () {
popup.style.display = 'block';
overlay.style.display = 'block';
popup.classList.remove('fadeOut');
popup.classList.add('fadeIn');
});
document.getElementById('close-popup').addEventListener('click', function () {
closePopup();
});
overlay.addEventListener('click', function () {
closePopup();
});
document
.getElementById('refresh-saves')
.addEventListener('click', async function () {
document.getElementById('loading').style.display = 'block';
await getOwnAndWish();
if (document.getElementById('family-library').checked) {
await getFamilyGame();
}
document.getElementById('loading').style.display = 'none';
updateCounts();
});
document.getElementById('clear-saves').addEventListener('click', function () {
let nullSaves = {
wishlist: [],
ownedApps: [],
familygameList: [],
};
GM_setValue('Saves', nullSaves);
updateCounts();
});
const isInfamily = document.getElementById('family-library');
if (localStorage.getItem('isInfamily') === 'true') {
isInfamily.checked = true;
}
isInfamily.addEventListener('change', function () {
if (this.checked) {
localStorage.setItem('isInfamily', 'true');
} else {
localStorage.removeItem('isInfamily');
}
});
const isSuspensionOff = document.getElementById('isSuspensionOff');
if (localStorage.getItem('isSuspensionOff') === 'true') {
isSuspensionOff.checked = true;
GM_addStyle('.suspension{display:none}');
}
isSuspensionOff.addEventListener('change', function () {
if (this.checked) {
localStorage.setItem('isSuspensionOff', 'true');
GM_addStyle('.suspension{display:none}');
} else {
GM_addStyle('.suspension{display:block}');
localStorage.removeItem('isSuspensionOff');
}
});
function updateCounts() {
const saves = GM_getValue('Saves');
document.getElementById(
'ownedAppsCount'
).innerHTML = `已加载${saves.ownedApps.length} 个库存游戏及DLC`;
document.getElementById(
'wishListCount'
).innerHTML = `已加载${saves.wishlist.length} 个愿望单游戏`;
document.getElementById(
'familyGameCount'
).innerHTML = `已加载${saves.familygameList.length} 个家庭库游戏`;
}
function closePopup() {
popup.classList.remove('fadeIn');
popup.classList.add('fadeOut');
popup.addEventListener(
'animationend',
function () {
if (popup.classList.contains('fadeOut')) {
overlay.style.display = 'none';
popup.style.display = 'none';
}
},
{ once: true }
);
}
}
//CSS样式
const style = document.createElement('style');
style.innerHTML = `
.popup-window {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
max-width: 500px;
background-color: white;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
z-index: 1000;
}
.popup-content {
padding: 20px;
font-size: 17px;
}
.close-btn {
position: absolute;
top: 10px;
right: 10px;
font-size: 20px;
cursor: pointer;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translate(-50%, -60%);
}
to {
opacity: 1;
transform: translate(-50%, -50%);
}
}
@keyframes fadeOut {
from {
opacity: 1;
transform: translate(-50%, -50%);
}
to {
opacity: 0;
transform: translate(-50%, -60%);
}
}
.fadeIn {
animation: fadeIn 0.5s forwards;
}
.fadeOut {
animation: fadeOut 0.5s forwards;
}
.overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
}
`;
document.head.appendChild(style);
//游戏状态标记-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 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) {
if (isAppOwned(appId)) {
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;
}
}
}
//加载存档
function load() {
var previousSave = GM_getValue('Saves');
if (previousSave !== undefined) {
Saves = GM_getValue('Saves', {
wishlist: [],
ownedApps: [],
familygameList: [],
});
} else {
GM_setValue('Saves', Saves);
}
}
//监听页面变化
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);
}
//Todo list:
//夜间模式
//侧栏收放
//中键快捷控制标签页
//可库+1标志
//设置内调整色号