Better SteamPY

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

目前為 2024-08-19 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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标志
//设置内调整色号