Better SteamPY

提供Steampy界面美化,功能增强,如库中已有游戏标记(支持家庭库及愿望单)等功能

当前为 2024-08-19 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==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">&times;</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标志
//设置内调整色号